/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to you under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.calcite.test;

import org.apache.calcite.DataContext;
import org.apache.calcite.DataContexts;
import org.apache.calcite.adapter.enumerable.EnumerableConvention;
import org.apache.calcite.adapter.enumerable.EnumerableLimit;
import org.apache.calcite.adapter.enumerable.EnumerableLimitSort;
import org.apache.calcite.adapter.enumerable.EnumerableRules;
import org.apache.calcite.adapter.enumerable.EnumerableSort;
import org.apache.calcite.config.CalciteConnectionConfig;
import org.apache.calcite.plan.Contexts;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptCost;
import org.apache.calcite.plan.RelOptCostImpl;
import org.apache.calcite.plan.RelOptPlanner;
import org.apache.calcite.plan.RelOptRule;
import org.apache.calcite.plan.RelOptRuleCall;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.plan.RelRule;
import org.apache.calcite.plan.RelTraitDef;
import org.apache.calcite.plan.RelTraitSet;
import org.apache.calcite.plan.hep.HepMatchOrder;
import org.apache.calcite.plan.hep.HepPlanner;
import org.apache.calcite.plan.hep.HepProgram;
import org.apache.calcite.plan.hep.HepProgramBuilder;
import org.apache.calcite.rel.RelCollation;
import org.apache.calcite.rel.RelCollationTraitDef;
import org.apache.calcite.rel.RelCollations;
import org.apache.calcite.rel.RelDistributionTraitDef;
import org.apache.calcite.rel.RelDistributions;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.Filter;
import org.apache.calcite.rel.core.Intersect;
import org.apache.calcite.rel.core.Join;
import org.apache.calcite.rel.core.JoinRelType;
import org.apache.calcite.rel.core.Minus;
import org.apache.calcite.rel.core.Project;
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Union;
import org.apache.calcite.rel.hint.HintPredicates;
import org.apache.calcite.rel.hint.HintStrategyTable;
import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.logical.LogicalCorrelate;
import org.apache.calcite.rel.logical.LogicalFilter;
import org.apache.calcite.rel.logical.LogicalIntersect;
import org.apache.calcite.rel.logical.LogicalMinus;
import org.apache.calcite.rel.logical.LogicalProject;
import org.apache.calcite.rel.logical.LogicalTableModify;
import org.apache.calcite.rel.logical.LogicalUnion;
import org.apache.calcite.rel.metadata.RelMetadataQuery;
import org.apache.calcite.rel.rules.AggregateExpandWithinDistinctRule;
import org.apache.calcite.rel.rules.AggregateExtractProjectRule;
import org.apache.calcite.rel.rules.AggregateProjectConstantToDummyJoinRule;
import org.apache.calcite.rel.rules.AggregateProjectMergeRule;
import org.apache.calcite.rel.rules.AggregateProjectPullUpConstantsRule;
import org.apache.calcite.rel.rules.AggregateReduceFunctionsRule;
import org.apache.calcite.rel.rules.CoerceInputsRule;
import org.apache.calcite.rel.rules.CoreRules;
import org.apache.calcite.rel.rules.DateRangeRules;
import org.apache.calcite.rel.rules.FilterAggregateTransposeRule;
import org.apache.calcite.rel.rules.FilterFlattenCorrelatedConditionRule;
import org.apache.calcite.rel.rules.FilterJoinRule;
import org.apache.calcite.rel.rules.FilterMultiJoinMergeRule;
import org.apache.calcite.rel.rules.FilterProjectTransposeRule;
import org.apache.calcite.rel.rules.JoinAssociateRule;
import org.apache.calcite.rel.rules.JoinCommuteRule;
import org.apache.calcite.rel.rules.LoptOptimizeJoinRule;
import org.apache.calcite.rel.rules.MeasureRules;
import org.apache.calcite.rel.rules.MultiJoin;
import org.apache.calcite.rel.rules.ProjectCorrelateTransposeRule;
import org.apache.calcite.rel.rules.ProjectFilterTransposeRule;
import org.apache.calcite.rel.rules.ProjectJoinTransposeRule;
import org.apache.calcite.rel.rules.ProjectMultiJoinMergeRule;
import org.apache.calcite.rel.rules.ProjectToWindowRule;
import org.apache.calcite.rel.rules.PruneEmptyRules;
import org.apache.calcite.rel.rules.PushProjector;
import org.apache.calcite.rel.rules.ReduceExpressionsRule;
import org.apache.calcite.rel.rules.ReduceExpressionsRule.ProjectReduceExpressionsRule;
import org.apache.calcite.rel.rules.SingleValuesOptimizationRules;
import org.apache.calcite.rel.rules.SortProjectTransposeRule;
import org.apache.calcite.rel.rules.SortUnionTransposeRule;
import org.apache.calcite.rel.rules.SpatialRules;
import org.apache.calcite.rel.rules.SubQueryRemoveRule;
import org.apache.calcite.rel.rules.UnionMergeRule;
import org.apache.calcite.rel.rules.ValuesReduceRule;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeSystemImpl;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexCorrelVariable;
import org.apache.calcite.rex.RexExecutorImpl;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
import org.apache.calcite.sql.SqlBasicFunction;
import org.apache.calcite.sql.SqlFunction;
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.fun.SqlLibrary;
import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.test.SqlTestFactory;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql2rel.RelDecorrelator;
import org.apache.calcite.test.SqlToRelTestBase.CustomCorrelate;
import org.apache.calcite.test.catalog.MockCatalogReader;
import org.apache.calcite.test.catalog.MockCatalogReaderExtended;
import org.apache.calcite.tools.Program;
import org.apache.calcite.tools.Programs;
import org.apache.calcite.tools.RelBuilder;
import org.apache.calcite.tools.RuleSet;
import org.apache.calcite.tools.RuleSets;
import org.apache.calcite.util.DateString;
import org.apache.calcite.util.Holder;
import org.apache.calcite.util.ImmutableBitSet;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.immutables.value.Value;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
import java.util.function.Predicate;

import static org.apache.calcite.test.SqlToRelTestBase.NL;

import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Unit test for rules in {@code org.apache.calcite.rel} and subpackages.
 *
 * <p>As input, the test supplies a SQL statement and a single rule; the SQL is
 * translated into relational algebra and then fed into a
 * {@link org.apache.calcite.plan.hep.HepPlanner}. The planner fires the rule on
 * every
 * pattern match in a depth-first left-to-right pre-order traversal of the tree
 * for as long as the rule continues to succeed in applying its transform. (For
 * rules which call transformTo more than once, only the last result is used.)
 * The plan before and after "optimization" is diffed against a .ref file using
 * {@link DiffRepository}.
 *
 * <p>Procedure for adding a new test case:
 *
 * <ol>
 * <li>Add a new public test method for your rule, following the existing
 * examples. You'll have to come up with an SQL statement to which your rule
 * will apply in a meaningful way. See {@link SqlToRelTestBase} class comments
 * for details on the schema.
 *
 * <li>Run the test. It should fail. Inspect the output in
 * {@code build/diffrepo/test/org/apache/calcite/test/RelOptRulesTest_actual.xml},
 * e.g.
 *
 * <blockquote>{@code
 * diff core/src/test/resources/org/apache/calcite/test/RelOptRulesTest.xml
 * build/diffrepo/test/org/apache/calcite/test/RelOptRulesTest_actual.xml
 * }</blockquote>
 *
 * <li>Verify that the "planBefore" is the correct translation of your SQL,
 * and that it contains the pattern on which your rule is supposed to fire.
 * If all is well, replace {@code src/test/resources/.../RelOptRulesTest.xml}
 * with the new {@code build/resources/.../RelOptRulesTest_actual.xml}.
 *
 * <li>Run the test again. It should fail again, but this time it should contain
 * a "planAfter" entry for your rule. Verify that your rule applied its
 * transformation correctly, and then update the
 * {@code src/test/resources/.../RelOptRulesTest.xml} file again.
 *
 * <li>Run the test one last time; this time it should pass.
 * </ol>
 */
class RelOptRulesTest extends RelOptTestBase {
  //~ Methods ----------------------------------------------------------------

  @Nullable
  private static DiffRepository diffRepos = null;

  @AfterAll
  public static void checkActualAndReferenceFiles() {
    if (diffRepos != null) {
      diffRepos.checkActualAndReferenceFiles();
    }
  }

  @Override RelOptFixture fixture() {
    RelOptFixture fixture = super.fixture()
        .withDiffRepos(DiffRepository.lookup(RelOptRulesTest.class));
    diffRepos = fixture.diffRepos();
    return fixture;
  }

  private static boolean skipItem(RexNode expr) {
    return expr instanceof RexCall
          && "item".equalsIgnoreCase(((RexCall) expr).getOperator().getName());
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5813">[CALCITE-5813]
   * Type inference for sql functions REPEAT, SPACE, XML_TRANSFORM,
   * and XML_EXTRACT is incorrect</a>. */
  @Test void testRepeat() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);

    final String sql = "select REPEAT('abc', 2)";
    fixture()
        .withFactory(
            t -> t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.BIG_QUERY))) // needed for REPEAT function
        .sql(sql)
        .withPlanner(hepPlanner)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5813">[CALCITE-5813]
   * Type inference for sql functions REPEAT, SPACE, XML_TRANSFORM,
   * and XML_EXTRACT is incorrect</a>. */
  @Test void testReplace() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);

    final String sql = "select REPLACE('abc', 'c', 'cd')";
    fixture()
        .sql(sql)
        .withPlanner(hepPlanner)
        .check();
  }

  private HepProgram createHypergraphProgram() {
    return new HepProgramBuilder().addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.JOIN_PROJECT_RIGHT_TRANSPOSE)
        .addRuleInstance(CoreRules.JOIN_PROJECT_LEFT_TRANSPOSE)
        .addRuleInstance(CoreRules.PROJECT_MERGE)
        .addRuleInstance(CoreRules.PROJECT_REMOVE)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7191">[CALCITE-7191]
   * Hypergraph creation with incorrect hyperedges</a>. */
  @Test void testHypergraph0() {
    HepProgram program = createHypergraphProgram();
    String innerJoinSql = "select a.ename, bc.name from bonus a inner join "
        + "(select b.empno, b.ename, c.name from emp b inner join dept c on b.deptno = c.deptno) bc "
        + "on a.ename = bc.ename";

    sql(innerJoinSql).withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7191">[CALCITE-7191]
   * Hypergraph creation with incorrect hyperedges</a>. */
  @Test void testHypergraph1() {
    HepProgram program = createHypergraphProgram();
    String innerJoinSql = "select a.ename, bcde.name "
        + "from bonus a "
        + "inner join ("
        + "  select b.empno, b.ename, c.name "
        + "  from emp b "
        + "  inner join dept c on b.deptno = c.deptno "
        + "  inner join emp_address d on d.empno = b.empno "
        + "  inner join salgrade e on b.sal = e.hisal"
        + ") bcde on a.ename = bcde.ename";

    sql(innerJoinSql).withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7191">[CALCITE-7191]
   * Hypergraph creation with incorrect hyperedges</a>. */
  @Test void testHypergraph2() {
    HepProgram program = createHypergraphProgram();
    String innerJoinSql = "select ab.ename, cdef.name "
        + "from (select a.ename from bonus a inner join emp b on a.ename = b.ename) ab "
        + "inner join ("
        + "  select c.empno, c.ename, d.name "
        + "  from emp c "
        + "  inner join dept d on c.deptno = d.deptno "
        + "  inner join emp_address e on e.empno = c.empno "
        + "  inner join salgrade f on c.sal = f.hisal"
        + ") cdef on ab.ename = cdef.ename";

    sql(innerJoinSql).withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5989">[CALCITE-5989]
   * Type inference for RPAD and LPAD functions (BIGQUERY) is incorrect</a>. */
  @Test void testRpad() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);

    final String sql = "select RPAD('abc', 8, 'A')";
    fixture()
        .withFactory(
            t -> t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.BIG_QUERY))) // needed for RPAD function
        .sql(sql)
        .withPlanner(hepPlanner)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5989">[CALCITE-5989]
   * Type inference for RPAD and LPAD functions (BIGQUERY) is incorrect</a>. */
  @Test void testLpad() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);

    final String sql = "select LPAD('abc', 8, 'A')";
    fixture()
        .withFactory(
            t -> t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.BIG_QUERY))) // needed for LPAD function
        .sql(sql)
        .withPlanner(hepPlanner)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5971">[CALCITE-5971]
   * Add the RelRule to rewrite the bernoulli sample as Filter</a>. */
  @Test void testSampleToFilter() {
    final String sql = "select deptno from emp tablesample bernoulli(50)";
    sql(sql)
        .withRule(CoreRules.SAMPLE_TO_FILTER)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6317">
   * [CALCITE-6317] Incorrect constant replacement when group keys are NULL</a>. */
  @Test void testPredicatePull() {
    final String sql = "select deptno, sal "
                 + "from emp "
                + "where deptno = 10 "
                + "group by rollup(sal, deptno)";
    sql(sql).withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5971">[CALCITE-5971]
   * Add the RelRule to rewrite the bernoulli sample as Filter</a>. */
  @Test void testSampleToFilterWithSeed() {
    final String sql = "select deptno from emp tablesample bernoulli(50) REPEATABLE(10)";
    sql(sql)
        .withRule(CoreRules.SAMPLE_TO_FILTER)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6887">[CALCITE-6887]
   * ReduceExpressionsRule applied to 'IN subquery' should make the values distinct
   * if the subquery is a Values composed of literals</a>. */
  @Test void testAggregateValuesRuleWithInRepeatingValues() {
    final String sql = "SELECT deptno, sal "
        + "FROM EMP "
        + "WHERE deptno IN (1,1,3,1,2,1,1,1,1,1,1,1,null,null,1,1,1,1,1,1,1)";

    sql(sql)
        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withRule(CoreRules.AGGREGATE_VALUES)
        .check();
  }

  @Test void testReduceInValuesNonOrdered() {
    final String sql = "SELECT deptno, sal "
        + "FROM EMP "
        + "WHERE deptno IN (1,1,3,1,2)";

    sql(sql)
        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withRule(CoreRules.AGGREGATE_VALUES)
        .checkUnchanged();
  }

  @Test void testAggregateValuesRuleWithRepeatingValues() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .values(new String[]{"v"}, 1, 1, 3, null, null)
        .distinct()
        .build();
    relFn(relFn)
        .withRule(CoreRules.AGGREGATE_VALUES)
        .check();
  }

  @Test void testAggregateValuesRuleWithAggregation() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .values(new String[]{"v1", "v2"}, 1, "a", 2, "b", 1, "a", 1, "b")
        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)),
            b.count(b.field(0)).as("V1_COUNT"))
        .build();
    relFn(relFn)
        .withRule(CoreRules.AGGREGATE_VALUES)
        .checkUnchanged();
  }

  @Test void testAggregateValuesRuleWithSameRowType() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .values(new String[]{"v1", "v2"}, 1, "a", 2, "b", 1, "a", 1, "b")
        .aggregate(b.groupKey(ImmutableBitSet.of(0, 1)))
        .build();
    relFn(relFn)
        .withRule(CoreRules.AGGREGATE_VALUES)
        .check();
  }

  @Test void testAggregateValuesRuleWithDifferentRowTypes() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .values(new String[]{"v1", "v2"}, 1, "a", 2, "b", 1, "a", 1, "b")
        .aggregate(b.groupKey(ImmutableBitSet.of(0)))
        .build();
    relFn(relFn)
        .withRule(CoreRules.AGGREGATE_VALUES)
        .checkUnchanged();
  }

  /**
   * Test case for <a href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6746">
   * [CALCITE-6746] Optimization rule ProjectWindowTranspose is unsound</a>. */
  @Test void testConstantWindow() {
    final String sql = "with empsalary(dept, empno, salary, enroll_date) as "
        + "(VALUES ('x', 10, 5200, DATE '2007-08-01'), (NULL, NULL, NULL, NULL))\n"
        + "select sum(salary) "
        + "OVER (order by enroll_date range between INTERVAL 365 DAYS preceding and "
        + "INTERVAL 365 DAYS following),\n"
        + "salary, enroll_date FROM empsalary";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW)
        .withRule(CoreRules.PROJECT_WINDOW_TRANSPOSE)
        .checkUnchanged();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5813">[CALCITE-5813]
   * Type inference for sql functions REPEAT, SPACE, XML_TRANSFORM,
   * and XML_EXTRACT is incorrect</a>. */
  @Test void testSpace() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);

    final String sql = "select SPACE(2001)";
    fixture()
        .withFactory(
            t -> t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.MYSQL))) // needed for SPACE function
        .sql(sql)
        .withPlanner(hepPlanner)
        .check();
  }

  @Test void testGroupByDateLiteralSimple() {
    final String query = "select avg(sal)\n"
        + "from emp\n"
        + "group by DATE '2022-01-01'";
    sql(query)
        .withRule(AggregateProjectConstantToDummyJoinRule.Config.DEFAULT.toRule())
        .check();
  }

  @Test void testGroupByBooleanLiteralSimple() {
    final String query = "select avg(sal)\n"
        + "from emp\n"
        + "group by true";
    sql(query)
        .withRule(AggregateProjectConstantToDummyJoinRule.Config.DEFAULT.toRule())
        .check();
  }

  @Test void testGroupByMultipleLiterals() {
    final String query = "select avg(sal)\n"
        + "from emp\n"
        + "group by false, deptno, true, true, empno, false, 'ab', DATE '2022-01-01'";
    sql(query)
        .withRule(AggregateProjectConstantToDummyJoinRule.Config.DEFAULT.toRule())
        .check();
  }

  @Test void testReduceNot() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.FILTER_REDUCE_EXPRESSIONS);

    final String sql = "select *\n"
        + "from (select (case when sal > 1000 then null else false end) as caseCol from emp)\n"
        + "where NOT(caseCol)";
    sql(sql).withPlanner(hepPlanner)
        .checkUnchanged();
  }

  @Test void testReduceNestedCaseWhen() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.FILTER_REDUCE_EXPRESSIONS);

    final String sql = "select sal\n"
            + "from emp\n"
            + "where case when (sal = 1000) then\n"
            + "(case when sal = 1000 then null else 1 end is null) else\n"
            + "(case when sal = 2000 then null else 1 end is null) end is true";
    sql(sql).withPlanner(hepPlanner)
        .check();
  }

  @Test void testDigestOfApproximateDistinctAggregateCall() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(AggregateProjectMergeRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.AGGREGATE_PROJECT_MERGE);

    final String sql = "select *\n"
            + "from (\n"
            + "select deptno, count(distinct empno) from emp group by deptno\n"
            + "union all\n"
            + "select deptno, approx_count_distinct(empno) from emp group by deptno)";
    sql(sql).withPlanner(hepPlanner)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1479">[CALCITE-1479]
   * AssertionError in ReduceExpressionsRule on multi-column IN
   * sub-query</a>. */
  @Test void testReduceCompositeInSubQuery() {
    final String sql = "select *\n"
        + "from emp\n"
        + "where (empno, deptno) in (\n"
        + "  select empno, deptno from (\n"
        + "    select empno, deptno\n"
        + "    from emp\n"
        + "    group by empno, deptno))\n"
        + "or deptno < 40 + 60";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderConfig(b -> b.withAggregateUnique(true))
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2865">[CALCITE-2865]
   * FilterProjectTransposeRule generates wrong traitSet when
   * copyFilter/copyProject is true</a>. */
  @Test void testFilterProjectTransposeRule() {
    List<RelOptRule> rules =
        Arrays.asList(
            // default: copyFilter=true, copyProject=true
            CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_PROJECT_TRANSPOSE.config
                .withOperandFor(Filter.class,
                    filter -> !RexUtil.containsCorrelation(filter.getCondition()),
                    Project.class, project -> true)
                .withCopyFilter(false)
                .withCopyProject(false)
                .toRule());

    for (RelOptRule rule : rules) {
      RelBuilder b = RelBuilder.create(RelBuilderTest.config().build());
      RelNode in = b
              .scan("EMP")
              .sort(-4) // salary desc
              .project(b.field(3)) // salary
              .filter(b.equals(b.field(0), b.literal(11500))) // salary = 11500
              .build();
      HepProgram program = new HepProgramBuilder()
              .addRuleInstance(rule)
              .build();
      HepPlanner hepPlanner = new HepPlanner(program);
      hepPlanner.setRoot(in);
      RelNode result = hepPlanner.findBestExp();

      // Verify LogicalFilter traitSet (must be [3 DESC])
      RelNode filter = result.getInput(0);
      RelCollation collation =
          filter.getTraitSet().getTrait(RelCollationTraitDef.INSTANCE);
      assertNotNull(collation);
      List<RelFieldCollation> fieldCollations = collation.getFieldCollations();
      assertThat(fieldCollations, hasSize(1));
      RelFieldCollation fieldCollation = fieldCollations.get(0);
      assertThat(fieldCollation.getFieldIndex(), is(3));
      assertThat(fieldCollation.getDirection(),
          is(RelFieldCollation.Direction.DESCENDING));
    }
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6873">[CALCITE-6873]
   * FilterProjectTransposeRule should not push the Filter past the Project
   * when the Filter contains a Subquery with correlation</a>. */
  @Test void testFilterProjectTransposeRule2() {
    final String sql = "select * from (select deptno from emp) as d\n"
        + "where NOT EXISTS (\n"
        + "  select count(*) from emp e where e.deptno = d.deptno)";
    sql(sql)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE)
        .checkUnchanged();
  }

  @Test void testFilterProjectTransposeRule3() {
    final String sql = "select * from (select deptno from emp) as d\n"
        + "where NOT EXISTS (\n"
        + "  select count(*) from emp e)";
    sql(sql)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6031">[CALCITE-6031]
   * Add the planner rule that pushes the Filter past a Sample</a>. */
  @Test void testFilterSampleTransposeWithBernoulli() {
    final String sql = "select deptno from emp tablesample bernoulli(50) where deptno > 10";
    sql(sql)
        .withRule(CoreRules.FILTER_SAMPLE_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6031">[CALCITE-6031]
   * Add the planner rule that pushes the Filter past a Sample</a>. */
  @Test void testFilterSampleTransposeWithSystem() {
    final String sql = "select deptno from emp tablesample system(50) where deptno > 10";
    sql(sql)
        .withRule(CoreRules.FILTER_SAMPLE_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6031">[CALCITE-6031]
   * Add the planner rule that pushes the Filter past a Sample</a>. */
  @Test void testFilterSampleTransposeWithSystemAndSeed() {
    final String sql = "select deptno from emp\n"
        + "tablesample system(50) repeatable(10) where deptno > 10";
    sql(sql)
        .withRule(CoreRules.FILTER_SAMPLE_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6878">[CALCITE-6878]
   * Implement FilterSortTransposeRule</a>. */
  @Test void testFilterSortTranspose() {
    final Function<RelBuilder, RelNode> relFn = b -> b
            .scan("EMP")
            .project(b.field(0))
            .sort(b.field(0))
            .filter(b.lessThan(b.field(0), b.literal(10)))
            .build();
    relFn(relFn).withRule(CoreRules.FILTER_SORT_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6878">[CALCITE-6878]
   * Implement FilterSortTransposeRule</a>. */
  @Test void testFilterSortTransposeFetch() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .project(b.field(0))
        .sortLimit(0, 1, b.field(0))
        .filter(b.lessThan(b.field(0), b.literal(10)))
        .build();
    relFn(relFn).withRule(CoreRules.FILTER_SORT_TRANSPOSE).checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6878">[CALCITE-6878]
   * Implement FilterSortTransposeRule</a>. */
  @Test void testFilterSortTransposeOffset() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .project(b.field(0))
        .sortLimit(1, 0, b.field(0))
        .filter(b.lessThan(b.field(0), b.literal(10)))
        .build();
    relFn(relFn).withRule(CoreRules.FILTER_SORT_TRANSPOSE).checkUnchanged();
  }

  @Test void testReduceOrCaseWhen() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.FILTER_REDUCE_EXPRESSIONS);

    final String sql = "select sal\n"
        + "from emp\n"
        + "where case when sal = 1000 then null else 1 end is null\n"
        + "OR case when sal = 2000 then null else 1 end is null";
    sql(sql).withPlanner(hepPlanner)
        .check();
  }

  @Test void testReduceNullableCase() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);

    final String sql = "SELECT CASE WHEN 1=2 "
        + "THEN cast((values(1)) as integer) "
        + "ELSE 2 end from (values(1))";
    sql(sql)
        .withExpand(true)
        .withPlanner(hepPlanner)
        .checkUnchanged();
  }

  @Test void testReduceNullableCase2() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);

    final String sql = "SELECT deptno, ename, CASE WHEN 1=2 "
        + "THEN substring(ename, 1, cast(2 as int)) ELSE NULL end from emp"
        + " group by deptno, ename, case when 1=2 then substring(ename,1, cast(2 as int))  else null end";
    sql(sql)
        .withExpand(true)
        .withPlanner(hepPlanner)
        .checkUnchanged();
  }

  @Test void testProjectToWindowRuleForMultipleWindows() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ProjectToWindowRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW);

    final String sql = "select\n"
        + " count(*) over(partition by empno order by sal) as count1,\n"
        + " count(*) over(partition by deptno order by sal) as count2,\n"
        + " sum(deptno) over(partition by empno order by sal) as sum1,\n"
        + " sum(deptno) over(partition by deptno order by sal) as sum2\n"
        + "from emp";
    sql(sql).withPlanner(hepPlanner)
        .check();
  }

  @Test void testUnionToDistinctRule() {
    final String sql = "select * from dept union select * from dept";
    sql(sql).withRule(CoreRules.UNION_TO_DISTINCT).check();
  }

  @Test void testExtractJoinFilterRule() {
    final String sql = "select 1 from emp inner join dept on emp.deptno=dept.deptno";
    sql(sql).withRule(CoreRules.JOIN_EXTRACT_FILTER).check();
  }

  @Test void testNotPushExpression() {
    final String sql = "select 1 from emp inner join dept\n"
        + "on emp.deptno=dept.deptno and emp.ename is not null";
    sql(sql).withRule(CoreRules.JOIN_PUSH_EXPRESSIONS)
        .withRelBuilderSimplify(false)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6011">[CALCITE-6011]
   * Add the planner rule that pushes the Filter past a Window</a>. */
  @Test void testNotPushFilterPastWindowWhenPredicateNotOnPartitionKey() {
    final String sql = "select * from\n"
        + "(select NAME, DEPTNO, count(*) over (partition by NAME) from dept) t\n"
        + "where DEPTNO = 0";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW)
        .withRule(
            CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_WINDOW_TRANSPOSE,
            CoreRules.PROJECT_REMOVE).check();
  }

  @Test void testAddRedundantSemiJoinRule() {
    final String sql = "select 1 from emp inner join dept on emp.deptno = dept.deptno";
    sql(sql).withRule(CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3890">[CALCITE-3890]
   * Derive IS NOT NULL predicates from a inner join and create filters with those predicates
   * as new inputs of the join</a>. */
  @Test void testJoinDeriveIsNotNullFilterRule1() {
    final String sql = "select t1.deptno from emp t1 inner join emp t2 on t1.mgr = t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).check();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should create IS NOT NULL filter for the right input of join, but not for
   * the left input since its child already has one. */
  @Test void testJoinDeriveIsNotNullFilterRule2() {
    final String sql = "select t1.deptno from (select * from emp where mgr is not null) t1 "
        + "join emp t2 on t1.mgr = t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).check();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should create IS NOT NULL filter in both sides. */
  @Test void testJoinDeriveIsNotNullFilterRule3() {
    final String sql = "select t1.deptno from emp t1 inner join emp t2 on t1.mgr > t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).check();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should create IS NOT NULL filter for both join keys. */
  @Test void testJoinDeriveIsNotNullFilterRule4() {
    final String sql = "select t1.deptno from empnullables t1 inner join\n"
        + "empnullables t2 on t1.ename = t2.ename and t1.mgr > t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).check();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should create IS NOT NULL filter for the right input of join, but not for
   * the left input since its ancestor already has one. */
  @Test void testJoinDeriveIsNotNullFilterRule5() {
    final String sql = "select t1.deptno from (select ename, deptno+1 as deptno from\n"
        + "empnullables where ename is not null) t1 inner join\n"
        + "empnullables t2 on t1.ename = t2.ename";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).check();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should not create IS NOT NULL filter in any side since it is full join. */
  @Test void testJoinDeriveIsNotNullFilterRule6() {
    final String sql = "select t1.deptno from emp t1 full join emp t2 on t1.mgr = t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).checkUnchanged();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should not create IS NOT NULL filter in any side since it is left join. */
  @Test void testJoinDeriveIsNotNullFilterRule7() {
    final String sql = "select t1.deptno from emp t1 left join emp t2 on t1.mgr = t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).checkUnchanged();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should not create IS NOT NULL filter in any side since it is right join. */
  @Test void testJoinDeriveIsNotNullFilterRule8() {
    final String sql = "select t1.deptno from emp t1 right join emp t2 on t1.mgr = t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).checkUnchanged();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should not create IS NOT NULL filter in both sides since they already have the filter. */
  @Test void testJoinDeriveIsNotNullFilterRule9() {
    final String sql = "select t1.deptno from (select * from emp where mgr is not null) t1\n"
        + "join (select * from emp where mgr is not null) t2 on t1.mgr = t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).checkUnchanged();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should not create IS NOT NULL filter in any side since the join condition is not strong. */
  @Test void testJoinDeriveIsNotNullFilterRule10() {
    final String sql = "select t1.deptno from emp t1 inner join emp t2\n"
        + "on t1.mgr is not distinct from t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).checkUnchanged();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should not create IS NOT NULL filter since it's cartesian product. */
  @Test void testJoinDeriveIsNotNullFilterRule11() {
    final String sql = "select t1.deptno from empnullables t1 inner join\n"
        + "empnullables t2 on t1.ename = t2.ename or 1 = 1";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).checkUnchanged();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should create IS NOT NULL filter for one of the join keys. */
  @Test void testJoinDeriveIsNotNullFilterRule12() {
    final String sql = "select t1.deptno from empnullables t1 inner join\n"
        + "empnullables t2 on t1.ename = t2.ename and t1.mgr is not distinct from t2.mgr";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).check();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should not create IS NOT NULL filter if join condition is not strong wrt
   * each key. */
  @Test void testJoinDeriveIsNotNullFilterRule13() {
    final String sql = "select t1.deptno from empnullables t1 inner join\n"
        + "empnullables t2 on coalesce(t1.ename, t2.ename) = 'abc'";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).checkUnchanged();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should not create IS NOT NULL filter if join condition is not strong wrt
   * each key. */
  @Test void testJoinDeriveIsNotNullFilterRule14() {
    final String sql = "select t1.deptno from empnullables t1 inner join\n"
        + "empnullables t2 on nvl(t1.ename, t2.ename) = 'abc'";
    sql(sql)
        .withFactory(t ->
            t.withOperatorTable(opTab -> SqlValidatorTest.operatorTableFor(SqlLibrary.ORACLE)))
        .withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).checkUnchanged();
  }

  /** As {@link #testJoinDeriveIsNotNullFilterRule1()};
   * should create IS NOT NULL filter only for the first operand of NULLIF. */
  @Test void testJoinDeriveIsNotNullFilterRule15() {
    final String sql = "select t1.deptno from empnullables t1 inner join\n"
        + "empnullables t2 on nullif(t1.ename, t2.ename) = 'abc'";
    sql(sql).withRule(CoreRules.JOIN_DERIVE_IS_NOT_NULL_FILTER_RULE).check();
  }

  @Test void testStrengthenJoinType() {
    // The "Filter(... , right.c IS NOT NULL)" above a left join is pushed into
    // the join, makes it an inner join, and then disappears because c is NOT
    // NULL.
    final String sql = "select *\n"
        + "from dept left join emp on dept.deptno = emp.deptno\n"
        + "where emp.deptno is not null and emp.sal > 100";
    sql(sql)
        .withDecorrelate(true)
        .withTrim(true)
        .withPreRule(CoreRules.PROJECT_MERGE,
            CoreRules.FILTER_PROJECT_TRANSPOSE)
        .withRule(CoreRules.FILTER_INTO_JOIN)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3170">[CALCITE-3170]
   * ANTI join on conditions push down generates wrong plan</a>. */
  @Test void testCanNotPushAntiJoinConditionsToLeft() {
    // build a rel equivalent to sql:
    // select * from emp
    // where emp.deptno
    // not in (select dept.deptno from dept where emp.deptno > 20)
    checkCanNotPushSemiOrAntiJoinConditionsToLeft(JoinRelType.ANTI);
  }

  @Test void testCanNotPushAntiJoinConditionsToRight() {
    // build a rel equivalent to sql:
    // select * from emp
    // where emp.deptno
    // not in (select dept.deptno from dept where dept.dname = 'ddd')
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .scan("DEPT")
        .antiJoin(
            b.call(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO")),
            b.equals(b.field(2, 1, "DNAME"),
                b.literal("ddd")))
        .project(b.field(0))
        .build();
    relFn(relFn).withRule(CoreRules.JOIN_CONDITION_PUSH).checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3171">[CALCITE-3171]
   * SemiJoin on conditions push down throws IndexOutOfBoundsException</a>. */
  @Test void testPushSemiJoinConditionsToLeft() {
    // build a rel equivalent to sql:
    // select * from emp
    // where emp.deptno
    // in (select dept.deptno from dept where emp.empno > 20)
    checkCanNotPushSemiOrAntiJoinConditionsToLeft(JoinRelType.SEMI);
  }

  private void checkCanNotPushSemiOrAntiJoinConditionsToLeft(JoinRelType type) {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode left = b.scan("EMP").build();
      RelNode right = b.scan("DEPT").build();
      return b.push(left)
          .push(right)
          .join(type,
              b.call(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
                  b.field(2, 0, "DEPTNO"),
                  b.field(2, 1, "DEPTNO")),
              b.greaterThan(RexInputRef.of(0, left.getRowType()),
                  b.literal(20)))
          .project(b.field(0))
          .build();
    };
    relFn(relFn).withRule(CoreRules.JOIN_PUSH_EXPRESSIONS).checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3979">[CALCITE-3979]
   * ReduceExpressionsRule might have removed CAST expression(s) incorrectly</a>. */
  @Test void testCastRemove() {
    final String sql = "select\n"
        + "case when cast(ename as double) < 5 then 0.0\n"
        + "     else coalesce(cast(ename as double), 1.0)\n"
        + "     end as t\n"
        + " from (\n"
        + "       select\n"
        + "          case when ename > 'abc' then ename\n"
        + "               else null\n"
        + "               end as ename from emp\n"
        + " )";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3887">[CALCITE-3887]
   * Filter and Join conditions may not need to retain nullability during simplifications</a>. */
  @Test void testPushSemiJoinConditions() {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode left = b.scan("EMP")
          .project(
              b.field("DEPTNO"),
              b.field("ENAME"))
          .build();
      RelNode right = b.scan("DEPT")
          .project(
              b.field("DEPTNO"),
              b.field("DNAME"))
          .build();

      b.push(left).push(right);

      RexInputRef ref1 = b.field(2, 0, "DEPTNO");
      RexInputRef ref2 = b.field(2, 1, "DEPTNO");
      RexInputRef ref3 = b.field(2, 0, "ENAME");
      RexInputRef ref4 = b.field(2, 1, "DNAME");

      // ref1 IS NOT DISTINCT FROM ref2
      RexCall cond1 =
          (RexCall) b.call(SqlStdOperatorTable.OR, b.equals(ref1, ref2),
              b.call(SqlStdOperatorTable.AND, b.isNull(ref1), b.isNull(ref2)));

      // ref3 IS NOT DISTINCT FROM ref4
      RexCall cond2 =
          (RexCall) b.call(SqlStdOperatorTable.OR, b.equals(ref3, ref4),
              b.call(SqlStdOperatorTable.AND, b.isNull(ref3), b.isNull(ref4)));

      RexNode cond = b.and(cond1, cond2);
      return b.semiJoin(cond)
          .project(b.field(0))
          .build();
    };

    relFn(relFn)
        .withRule(
            CoreRules.JOIN_PUSH_EXPRESSIONS,
            CoreRules.SEMI_JOIN_PROJECT_TRANSPOSE,
            CoreRules.JOIN_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testFullOuterJoinSimplificationToLeftOuter() {
    final String sql = "select 1 from sales.dept d full outer join sales.emp e\n"
        + "on d.deptno = e.deptno\n"
        + "where d.name = 'Charlie'";
    sql(sql).withRule(CoreRules.FILTER_INTO_JOIN).check();
  }

  @Test void testFullOuterJoinSimplificationToRightOuter() {
    final String sql = "select 1 from sales.dept d full outer join sales.emp e\n"
        + "on d.deptno = e.deptno\n"
        + "where e.sal > 100";
    sql(sql).withRule(CoreRules.FILTER_INTO_JOIN).check();
  }

  @Test void testFullOuterJoinSimplificationToInner() {
    final String sql = "select 1 from sales.dept d full outer join sales.emp e\n"
        + "on d.deptno = e.deptno\n"
        + "where d.name = 'Charlie' and e.sal > 100";
    sql(sql).withRule(CoreRules.FILTER_INTO_JOIN).check();
  }

  @Test void testLeftOuterJoinSimplificationToInner() {
    final String sql = "select 1 from sales.dept d left outer join sales.emp e\n"
        + "on d.deptno = e.deptno\n"
        + "where e.sal > 100";
    sql(sql).withRule(CoreRules.FILTER_INTO_JOIN).check();
  }

  @Test void testRightOuterJoinSimplificationToInner() {
    final String sql = "select 1 from sales.dept d right outer join sales.emp e\n"
        + "on d.deptno = e.deptno\n"
        + "where d.name = 'Charlie'";
    sql(sql).withRule(CoreRules.FILTER_INTO_JOIN).check();
  }

  @Test void testPushAboveFiltersIntoInnerJoinCondition() {
    final String sql = ""
        + "select * from sales.dept d inner join sales.emp e\n"
        + "on d.deptno = e.deptno and d.deptno > e.mgr\n"
        + "where d.deptno > e.mgr";
    sql(sql).withRule(CoreRules.FILTER_INTO_JOIN).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3225">[CALCITE-3225]
   * JoinToMultiJoinRule should not match SEMI/ANTI LogicalJoin</a>. */
  @Test void testJoinToMultiJoinDoesNotMatchSemiJoin() {
    // build a rel equivalent to sql:
    // select * from
    // (select * from emp join dept ON emp.deptno = emp.deptno) t
    // where emp.job in (select job from bonus)
    checkJoinToMultiJoinDoesNotMatchSemiOrAntiJoin(JoinRelType.SEMI);
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3225">[CALCITE-3225]
   * JoinToMultiJoinRule should not match SEMI/ANTI LogicalJoin</a>. */
  @Test void testJoinToMultiJoinDoesNotMatchAntiJoin() {
    // build a rel equivalent to sql:
    // select * from
    // (select * from emp join dept ON emp.deptno = emp.deptno) t
    // where not exists (select job from bonus where emp.job = bonus.job)
    checkJoinToMultiJoinDoesNotMatchSemiOrAntiJoin(JoinRelType.ANTI);
  }

  private void checkJoinToMultiJoinDoesNotMatchSemiOrAntiJoin(JoinRelType type) {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode left = b.scan("EMP").build();
      RelNode right = b.scan("DEPT").build();
      RelNode semiRight = b.scan("BONUS").build();
      return b.push(left)
          .push(right)
          .join(JoinRelType.INNER,
              b.equals(b.field(2, 0, "DEPTNO"),
                  b.field(2, 1, "DEPTNO")))
          .push(semiRight)
          .join(type,
              b.equals(b.field(2, 0, "JOB"),
                  b.field(2, 1, "JOB")))
          .build();
    };
    relFn(relFn).withRule(CoreRules.JOIN_TO_MULTI_JOIN).check();
  }

  @Test void testPushFilterPastAgg() {
    final String sql = "select dname, c from\n"
        + "(select name dname, count(*) as c from dept group by name) t\n"
        + " where dname = 'Charlie'";
    sql(sql).withRule(CoreRules.FILTER_AGGREGATE_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6011">[CALCITE-6011]
   * Add the planner rule that pushes the Filter past a Window</a>. */
  @Test void testPushFilterPastWindowWithOnePartitionColumn() {
    final String sql = "select * from\n"
        + "(select NAME, DEPTNO, count(*) over (partition by DEPTNO) from dept)\n"
        + "where DEPTNO > 0";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW)
        .withRule(
            CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_WINDOW_TRANSPOSE,
            CoreRules.PROJECT_REMOVE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6011">[CALCITE-6011]
   * Add the planner rule that pushes the Filter past a Window</a>. */
  @Test void testPushFilterPastWindowWithTwoPartitionColumns() {
    final String sql = "select * from\n"
        + "(select NAME, DEPTNO, count(*) over (partition by NAME, DEPTNO) from dept)\n"
        + "where DEPTNO > 0";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW)
        .withRule(
            CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_WINDOW_TRANSPOSE,
            CoreRules.PROJECT_REMOVE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6011">[CALCITE-6011]
   * Add the planner rule that pushes the Filter past a Window</a>. */
  @Test void testPushFilterPastWindowWithDoubleWindows() {
    final String sql = "select * from\n"
        + "(select NAME, DEPTNO, count(*) over (partition by NAME, DEPTNO) as cnt,\n"
        + "sum(1) over (partition by DEPTNO) as all_sum from dept) t\n"
        + "where DEPTNO = 1";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW)
        .withRule(
            CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_WINDOW_TRANSPOSE,
            CoreRules.PROJECT_REMOVE).check();
  }

  private RelOptFixture basePushFilterPastAggWithGroupingSets() {
    return sql("${sql}")
        .withPreRule(CoreRules.PROJECT_MERGE,
            CoreRules.FILTER_PROJECT_TRANSPOSE)
        .withRule(CoreRules.FILTER_AGGREGATE_TRANSPOSE);
  }

  @Test void testPushFilterPastAggWithGroupingSets1() {
    basePushFilterPastAggWithGroupingSets().checkUnchanged();
  }

  @Test void testPushFilterPastAggWithGroupingSets2() {
    basePushFilterPastAggWithGroupingSets().check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-434">[CALCITE-434]
   * FilterAggregateTransposeRule loses conditions that cannot be pushed</a>. */
  @Test void testPushFilterPastAggTwo() {
    final String sql = "select dept1.c1 from (\n"
        + "select dept.name as c1, count(*) as c2\n"
        + "from dept where dept.name > 'b' group by dept.name) dept1\n"
        + "where dept1.c1 > 'c' and (dept1.c2 > 30 or dept1.c1 < 'z')";
    sql(sql).withRule(CoreRules.FILTER_AGGREGATE_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-799">[CALCITE-799]
   * Incorrect result for {@code HAVING count(*) > 1}</a>. */
  @Test void testPushFilterPastAggThree() {
    final String sql = "select deptno from emp\n"
        + "group by deptno having count(*) > 1";
    sql(sql).withRule(CoreRules.FILTER_AGGREGATE_TRANSPOSE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1109">[CALCITE-1109]
   * FilterAggregateTransposeRule pushes down incorrect condition</a>. */
  @Test void testPushFilterPastAggFour() {
    final String sql = "select emp.deptno, count(*) from emp where emp.sal > '12'\n"
        + "group by emp.deptno";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_FILTER_TRANSPOSE)
        .withRule(CoreRules.FILTER_AGGREGATE_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5425">[CALCITE-5425]
   * Should not pushdown Filter through Aggregate without group keys</a>. */
  @Test void testDoNotPushFilterThroughAggregateWithoutGroupKeys() {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode aggregate = b
          .scan("EMP")
          .aggregate(b.groupKey(), b.countStar(null))
          .build();

      // The reason why we use 'create' instead of 'RelBuilder#filter' is to prevent RelBuilder
      // optimizing in 'filter', which would transform the RelBuilder to 'empty()' when the
      // Filter is always false, this way, we won't be able to reproduce the problem
      // described in CALCITE-5425.
      return LogicalFilter.create(aggregate, b.getRexBuilder().makeLiteral(false));
    };
    relFn(relFn).withRule(FilterAggregateTransposeRule.Config.DEFAULT.toRule()).checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-448">[CALCITE-448]
   * FilterIntoJoinRule creates filters containing invalid RexInputRef</a>. */
  @Test void testPushFilterPastProject() {
    final FilterJoinRule.Predicate predicate =
        (join, joinType, exp) -> joinType != JoinRelType.INNER;
    final FilterJoinRule.JoinConditionPushRule join =
        CoreRules.JOIN_CONDITION_PUSH.config
            .withPredicate(predicate)
            .withDescription("FilterJoinRule:no-filter")
            .as(FilterJoinRule.JoinConditionPushRule.JoinConditionPushRuleConfig.class)
            .toRule();
    final FilterJoinRule.FilterIntoJoinRule filterOnJoin =
        CoreRules.FILTER_INTO_JOIN.config
            .withSmart(true)
            .withPredicate(predicate)
            .as(FilterJoinRule.FilterIntoJoinRule.FilterIntoJoinRuleConfig.class)
            .toRule();
    final HepProgram program =
        HepProgram.builder()
            .addGroupBegin()
            .addRuleInstance(CoreRules.FILTER_PROJECT_TRANSPOSE)
            .addRuleInstance(join)
            .addRuleInstance(filterOnJoin)
            .addGroupEnd()
            .build();
    final String sql = "select a.name\n"
        + "from dept a\n"
        + "left join dept b on b.deptno > 10\n"
        + "right join dept c on b.deptno > 10\n";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_MERGE)
        .withProgram(program)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4499">[CALCITE-4499]
   * FilterJoinRule misses opportunity to push filter to semijoin input</a>. */
  @Test void testPushFilterSemijoin() {
    final FilterJoinRule.Predicate predicate =
        (join, joinType, exp) -> joinType != JoinRelType.INNER;
    final FilterJoinRule.JoinConditionPushRule join =
        CoreRules.JOIN_CONDITION_PUSH.config
            .withPredicate(predicate)
            .withDescription("FilterJoinRule:no-filter")
            .as(FilterJoinRule.JoinConditionPushRule.JoinConditionPushRuleConfig.class)
            .toRule();

    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode left = b.scan("DEPT").build();
      RelNode right = b.scan("EMP").build();
      return b.push(left)
          .push(right)
          .semiJoin(
              b.and(
                  b.equals(b.field(2, 0, 0),
                      b.field(2, 1, 7)),
                  b.equals(b.field(2, 1, 5),
                      b.literal(100))))
          .project(b.field(1))
          .build();
    };

    relFn(relFn).withRule(join).check();
  }

  @Test void testSemiJoinProjectTranspose() {
    // build a rel equivalent to sql:
    // select a.name from dept a
    // where a.deptno in (select b.deptno * 2 from dept);
    checkSemiOrAntiJoinProjectTranspose(JoinRelType.SEMI);
  }

  @Test void testAntiJoinProjectTranspose() {
    // build a rel equivalent to sql:
    // select a.name from dept a
    // where a.deptno not in (select b.deptno * 2 from dept);
    checkSemiOrAntiJoinProjectTranspose(JoinRelType.ANTI);
  }

  private void checkSemiOrAntiJoinProjectTranspose(JoinRelType type) {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode left = b.scan("DEPT").build();
      RelNode right = b.scan("DEPT")
          .project(
              b.call(
                  SqlStdOperatorTable.MULTIPLY, b.literal(2), b.field(0)))
          .aggregate(b.groupKey(ImmutableBitSet.of(0))).build();

      return b.push(left)
          .push(right)
          .join(type,
              b.equals(b.field(2, 0, 0),
                  b.field(2, 1, 0)))
          .project(b.field(1))
          .build();
    };
    relFn(relFn).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testJoinProjectTranspose1() {
    final String sql = "select a.name\n"
        + "from dept a\n"
        + "left join dept b on b.deptno > 10\n"
        + "right join dept c on b.deptno > 10\n";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_JOIN_TRANSPOSE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.JOIN_PROJECT_LEFT_TRANSPOSE_INCLUDE_OUTER,
            CoreRules.PROJECT_MERGE,
            CoreRules.JOIN_PROJECT_RIGHT_TRANSPOSE_INCLUDE_OUTER,
            CoreRules.JOIN_PROJECT_LEFT_TRANSPOSE_INCLUDE_OUTER,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1338">[CALCITE-1338]
   * JoinProjectTransposeRule should not pull a literal above the
   * null-generating side of a join</a>. */
  @Test void testJoinProjectTranspose2() {
    final String sql = "select *\n"
        + "from dept a\n"
        + "left join (select name, 1 from dept) as b\n"
        + "on a.name = b.name";
    sql(sql)
        .withRule(CoreRules.JOIN_PROJECT_RIGHT_TRANSPOSE_INCLUDE_OUTER)
        .checkUnchanged();
  }

  /** As {@link #testJoinProjectTranspose2()};
   * should not transpose since the left project of right join has literal. */
  @Test void testJoinProjectTranspose3() {
    final String sql = "select *\n"
        + "from (select name, 1 from dept) as a\n"
        + "right join dept b\n"
        + "on a.name = b.name";
    sql(sql)
        .withRule(CoreRules.JOIN_PROJECT_LEFT_TRANSPOSE_INCLUDE_OUTER)
        .checkUnchanged();
  }

  /** As {@link #testJoinProjectTranspose2()};
   * should not transpose since the right project of left join has not-strong
   * expression {@code y is not null}. */
  @Test void testJoinProjectTranspose4() {
    final String sql = "select *\n"
        + "from dept a\n"
        + "left join (select x name, y is not null from\n"
        + "(values (2, cast(null as integer)), (2, 1)) as t(x, y)) b\n"
        + "on a.name = b.name";
    sql(sql)
        .withRule(CoreRules.JOIN_PROJECT_RIGHT_TRANSPOSE_INCLUDE_OUTER)
        .checkUnchanged();
  }

  /** As {@link #testJoinProjectTranspose2()};
   * should not transpose since the right project of left join has not-strong
   * expression {@code 1 + 1}. */
  @Test void testJoinProjectTranspose5() {
    final String sql = "select *\n"
        + "from dept a\n"
        + "left join (select name, 1 + 1 from dept) as b\n"
        + "on a.name = b.name";
    sql(sql)
        .withRule(CoreRules.JOIN_PROJECT_RIGHT_TRANSPOSE_INCLUDE_OUTER)
        .checkUnchanged();
  }

  /** As {@link #testJoinProjectTranspose2()};
   * should not transpose since both the left project and right project have
   * literal. */
  @Test void testJoinProjectTranspose6() {
    final String sql = "select *\n"
        + "from (select name, 1 from dept) a\n"
        + "full join (select name, 1 from dept) as b\n"
        + "on a.name = b.name";
    sql(sql)
        .withRule(CoreRules.JOIN_PROJECT_RIGHT_TRANSPOSE_INCLUDE_OUTER)
        .checkUnchanged();
  }

  /** As {@link #testJoinProjectTranspose2()};
   * Should transpose since all expressions in the right project of left join
   * are strong. */
  @Test void testJoinProjectTranspose7() {
    final String sql = "select *\n"
        + "from dept a\n"
        + "left join (select name from dept) as b\n"
        + " on a.name = b.name";
    sql(sql)
        .withRule(CoreRules.JOIN_PROJECT_RIGHT_TRANSPOSE_INCLUDE_OUTER)
        .check();
  }

  /** As {@link #testJoinProjectTranspose2()};
   * should transpose since all expressions including
   * {@code deptno > 10 and cast(null as boolean)} in the right project of left
   * join are strong. */
  @Test void testJoinProjectTranspose8() {
    final String sql = "select *\n"
        + "from dept a\n"
        + "left join (\n"
        + "  select name, deptno > 10 and cast(null as boolean)\n"
        + "  from dept) as b\n"
        + "on a.name = b.name";
    sql(sql)
        .withRule(CoreRules.JOIN_PROJECT_RIGHT_TRANSPOSE_INCLUDE_OUTER)
        .check();
  }

  @Test void testJoinProjectTransposeWindow() {
    final String sql = "select *\n"
        + "from dept a\n"
        + "join (select rank() over (order by name) as r, 1 + 1 from dept) as b\n"
        + "on a.name = b.r";
    sql(sql)
        .withRule(CoreRules.JOIN_PROJECT_BOTH_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-889">[CALCITE-889]
   * Implement SortUnionTransposeRule</a>. */
  @Test void testSortUnionTranspose() {
    final String sql = "select a.name from dept a\n"
        + "union all\n"
        + "select b.name from dept b\n"
        + "order by name limit 10";
    sql(sql)
        .withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE,
            CoreRules.SORT_UNION_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-889">[CALCITE-889]
   * Implement SortUnionTransposeRule</a>. */
  @Test void testSortUnionTranspose2() {
    final String sql = "select a.name from dept a\n"
        + "union all\n"
        + "select b.name from dept b\n"
        + "order by name";
    sql(sql)
        .withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE,
            CoreRules.SORT_UNION_TRANSPOSE_MATCH_NULL_FETCH)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-987">[CALCITE-987]
   * Push limit 0 will result in an infinite loop</a>. */
  @Test void testSortUnionTranspose3() {
    final String sql = "select a.name from dept a\n"
        + "union all\n"
        + "select b.name from dept b\n"
        + "order by name limit 0";
    sql(sql)
        .withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE,
            CoreRules.SORT_UNION_TRANSPOSE_MATCH_NULL_FETCH)
        .check();
  }

  @Test void testSortRemovalAllKeysConstant() {
    final String sql = "select count(*) as c\n"
        + "from sales.emp\n"
        + "where deptno = 10\n"
        + "group by deptno, sal\n"
        + "order by deptno desc nulls last";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_CONSTANT_KEYS)
        .check();
  }

  @Test void testSortRemovalOneKeyConstant() {
    final String sql = "select count(*) as c\n"
        + "from sales.emp\n"
        + "where deptno = 10\n"
        + "group by deptno, sal\n"
        + "order by deptno, sal desc nulls first";
    sql(sql)
        // Use VolcanoPlanner with collation to ensure the correct creation of the new Sort
        .withVolcanoPlanner(false,  p -> {
          p.addRelTraitDef(RelCollationTraitDef.INSTANCE);
          RelOptUtil.registerDefaultRules(p, false, false);
        })
        .withRule(CoreRules.SORT_REMOVE_CONSTANT_KEYS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6507">[CALCITE-6507]
   * Random functions are incorrectly considered deterministic</a>. */
  @Test void testSortRemoveConstantKeyDoesNotRemoveOrderByRand() {
    final String sql = "SELECT ename FROM emp ORDER BY RAND()";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_CONSTANT_KEYS)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6507">[CALCITE-6507]
   * Random functions are incorrectly considered deterministic</a>. */
  @Test void testSortRemoveConstantKeyDoesNotRemoveOrderByRandInteger() {
    final String sql = "SELECT ename FROM emp ORDER BY RAND_INTEGER(2)";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_CONSTANT_KEYS)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6507">[CALCITE-6507]
   * Random functions are incorrectly considered deterministic</a>. */
  @Test void testSortRemoveConstantKeyDoesNotRemoveOrderByRandom() {
    final String sql = "SELECT ename FROM emp ORDER BY RANDOM()";
    sql(sql)
        .withFactory(f ->
            f.withOperatorTable(opTab ->
                SqlValidatorTest.operatorTableFor(SqlLibrary.POSTGRESQL)))
        .withRule(CoreRules.SORT_REMOVE_CONSTANT_KEYS)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-873">[CALCITE-873]
   * SortRemoveConstantKeysRule should remove NULL literal sort keys
   * (e.g. ORDER BY NULL)</a>. */
  @Test void testSortRemoveConstantKeyWhenOrderByIsNull() {
    final String sql = "SELECT * FROM emp ORDER BY deptno, null, empno";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_CONSTANT_KEYS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5994">[CALCITE-5994]
   * Add optimization rule to remove Sort when its input's row number
   * is less or equal to one</a>. */
  @Test void testSortRemoveWhenAggregateMaxRowCntIsOne() {
    final String sql = "select count(*) as c\n"
        + "from sales.emp order by c";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5994">[CALCITE-5994]
   * Add optimization rule to remove Sort when its input's row number
   * is less or equal to one</a>. */
  @Test void testSortRemoveWhenLimitMaxRowCntIsOne() {
    final String sql = "select *\n"
        + "from (select * from sales.emp limit 1)\n"
        + "order by deptno";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT,
            CoreRules.SORT_PROJECT_TRANSPOSE,
            CoreRules.PROJECT_REMOVE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6009">[CALCITE-6009]
   * Add optimization to remove redundant LIMIT that is more than
   * input row count</a>. */
  @Test void testSortRemoveWhenInputValuesMaxRowCntLessOrEqualLimitFetch() {
    final String sql = "select * from\n"
        + "(VALUES 1,2,3,4,5,6) as t1 limit 10";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7181">[CALCITE-7181]
   * FETCH in SortRemoveRedundantRule do not support BIGINT</a>. */
  @Test void testSortRemoveWhenInputValuesMaxRowCntLessOrEqualLimitFetch2() {
    final String sql = "select * from\n"
        // The maximum value of BIGINT is   9223372036854775807.
        + "(VALUES 1,2,3,4,5,6) as t1 limit 9823372036854775807";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6009">[CALCITE-6009]
   * Add optimization to remove redundant LIMIT that is more than input
   * row count</a>. */
  @Test void testSortRemoveWhenInputAggregateMaxRowCntLessOrEqualLimitFetch() {
    final String sql = "select count(*) as c\n"
        + "from sales.emp limit 20";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6009">[CALCITE-6009]
   * Add optimization to remove redundant LIMIT that is more than input
   * row count</a>. */
  @Test void testSortRemoveWhenHasOffset() {
    final String sql = "select * from\n"
        + "(select * from sales.emp limit 10) t limit 20 offset 1";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6038">[CALCITE-6038]
   * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
   * and there is no 'OFFSET' clause</a>. */
  @Test void testSortRemoveWhenIsOrderAndLimit() {
    final String sql = "SELECT count(*) FROM sales.emp ORDER BY 1 LIMIT 10";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6038">[CALCITE-6038]
   * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
   * and there is no 'OFFSET' clause</a>. */
  @Test void testSortNotRemoveWhenIsOrderAndLimit() {
    final String sql = "select * from\n"
        + "(SELECT * FROM sales.emp limit 100)\n"
        + "ORDER BY 1 LIMIT 10";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6038">[CALCITE-6038]
   * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
   * and there is no 'OFFSET' clause</a>. */
  @Test void testSortNotRemoveWhenLimitFetchIsZeroHasOrder() {
    final String sql = "select * from\n"
        + "(SELECT * FROM sales.emp limit 1)\n"
        + "ORDER BY 1 LIMIT 0";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6038">[CALCITE-6038]
   * Remove 'ORDER BY ... LIMIT n' when input has at most one row, n >= 1,
   * and there is no 'OFFSET' clause</a>. */
  @Test void testSortNotRemoveWhenLimitFetchIsZeroWithoutOrder() {
    final String sql = "SELECT count(*) FROM sales.emp LIMIT 0";
    sql(sql)
        .withRule(CoreRules.SORT_REMOVE_REDUNDANT)
        .checkUnchanged();
  }

  /** Tests that an {@link EnumerableLimit} and {@link EnumerableSort} are
   * replaced by an {@link EnumerableLimitSort}, per
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3920">[CALCITE-3920]
   * Improve ORDER BY computation in Enumerable convention by exploiting
   * LIMIT</a>. */
  @Test void testLimitSort() {
    final String sql = "select mgr from sales.emp\n"
        + "union select mgr from sales.emp\n"
        + "order by mgr limit 10 offset 5";
    final RelOptFixture fixture = sql(sql)
        .withVolcanoPlanner(false)
        .withDecorrelate(true);
    RelNode rel = fixture.toRel();

    String planBefore = NL + RelOptUtil.toString(rel);
    final DiffRepository diffRepos = fixture.diffRepos;
    diffRepos.assertEquals("planBefore", "${planBefore}", planBefore);

    RuleSet ruleSet =
        RuleSets.ofList(EnumerableRules.ENUMERABLE_SORT_RULE,
            EnumerableRules.ENUMERABLE_LIMIT_RULE,
            EnumerableRules.ENUMERABLE_LIMIT_SORT_RULE,
            EnumerableRules.ENUMERABLE_PROJECT_RULE,
            EnumerableRules.ENUMERABLE_FILTER_RULE,
            EnumerableRules.ENUMERABLE_UNION_RULE,
            EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
    Program program = Programs.of(ruleSet);

    RelTraitSet toTraits =
        rel.getCluster().traitSet()
            .replace(0, EnumerableConvention.INSTANCE);

    RelNode relAfter =
        program.run(fixture.planner, rel, toTraits,
            Collections.emptyList(), Collections.emptyList());

    String planAfter = NL + RelOptUtil.toString(relAfter);
    diffRepos.assertEquals("planAfter", "${planAfter}", planAfter);
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5940">[CALCITE-5940]
   * Add the Rule to merge Limit</a>. */
  @Test void testLimitMerge() {
    final String sql = "select deptno from\n"
        + "(select deptno from emp where sal > 100 limit 10000)\n"
        + "limit 10";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_REMOVE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5940">[CALCITE-5940]
   * Add the Rule to merge Limit</a>. */
  @Test void testLimitMerge1() {
    final String sql = "select deptno from\n"
        + "(select deptno from emp where sal > 100 order by sal desc limit 10000)\n"
        + "limit 10";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5940">[CALCITE-5940]
   * Add the Rule to merge Limit</a>. */
  @Test void testLimitMerge2() {
    final String sql = "select deptno from\n"
        + "(select deptno from emp where sal > 100 order by sal desc limit 10)\n"
        + "limit 10000";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5940">[CALCITE-5940]
   * Add the Rule to merge Limit</a>. */
  @Test void testLimitMerge3() {
    final String sql = "select deptno from\n"
        + "(select deptno from emp where sal > 100 limit 100)\n"
        + "order by deptno desc";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_REMOVE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5940">[CALCITE-5940]
   * Add the Rule to merge Limit</a>. */
  @Test void testLimitMerge4() {
    final String sql = "select deptno from\n"
        + "(select deptno from emp where sal > 100 order by sal desc limit 10)\n"
        + "order by deptno limit 10000";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5940">[CALCITE-5940]
   * Add the Rule to merge Limit</a>. */
  @Test void testLimitMerge5() {
    final String sql = "select deptno from\n"
        + "(select deptno from emp where sal > 100 order by sal desc limit 10000)\n"
        + "order by deptno limit 10";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5940">[CALCITE-5940]
   * Add the Rule to merge Limit</a>. */
  @Test void testLimitMerge6() {
    final String sql = "select deptno from\n"
        + "(select deptno from emp where sal > 100 offset 100)\n"
        + "limit 10";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.LIMIT_MERGE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7176">[CALCITE-7176]
   * FETCH and OFFSET in SortMergeRule do not support BIGINT</a>. */
  @Test void testLimitMergeBigint() {
    final String sql = "select deptno from\n"
        + "(select deptno from emp where sal > 100 limit 3100000000)\n"
        + "limit 3000000000";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.LIMIT_MERGE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testSemiJoinRuleExists() {
    final String sql = "select * from dept where exists (\n"
        + "  select * from emp\n"
        + "  where emp.deptno = dept.deptno\n"
        + "  and emp.sal > 100)";
    sql(sql)
        .withExpand(true)
        .withDecorrelate(true)
        .withTrim(true)
        .withRelBuilderConfig(b -> b.withPruneInputOfAggregate(true))
        .withPreRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_INTO_JOIN,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .check();
  }

  @Test void testIncorrectInType() {
    final String sql = "select ename from emp "
        + "  where ename in ( 'Sebastian' ) and ename = 'Sebastian' and deptno < 100";
    sql(sql)
        .withTrim(true)
        .withRule()
        .checkUnchanged();
  }

  @Test void testSemiJoinRule() {
    final String sql = "select dept.* from dept join (\n"
        + "  select distinct deptno from emp\n"
        + "  where sal > 100) using (deptno)";
    sql(sql)
        .withDecorrelate(true)
        .withTrim(true)
        .withPreRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_INTO_JOIN,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .check();
  }

  @Test void testSemiJoinRuleDoNotMatchAggregate() {
    final String sql = "select *\n"
        + "from emp\n"
        + "where exists(select * from dept where emp.deptno = dept.deptno)";
    sql(sql)
        .withExpand(true)
        .withDecorrelate(true)
        .withPreRule(CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.JOIN_ON_UNIQUE_TO_SEMI_JOIN)
        .checkUnchanged();
  }

  @Test void testSemiJoinRuleWithJoinOnUniqueInput() {
    final String sql = "select *\n"
        + "from emp\n"
        + "where exists(select * from dept where emp.deptno = dept.deptno)";
    sql(sql)
        .withExpand(true)
        .withDecorrelate(true)
        .withTrim(true)
        .withRule(CoreRules.JOIN_ON_UNIQUE_TO_SEMI_JOIN)
        .check();
  }

  /**
   * Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5391">[CALCITE-5391]
   * JoinOnUniqueToSemiJoinRule should preserve field names, if possible</a>. */
  @Test void testSemiJoinRuleWithJoinOnUniqueInputWithAlias() {
    final String sql = "select emp.deptno as department_id, emp.sal as salary\n"
        + "from emp\n"
        + "where exists(select * from dept where emp.deptno = dept.deptno)";
    sql(sql)
        .withExpand(true)
        .withDecorrelate(true)
        .withTrim(true)
        .withRule(CoreRules.JOIN_ON_UNIQUE_TO_SEMI_JOIN)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1495">[CALCITE-1495]
   * SemiJoinRule should not apply to RIGHT and FULL JOIN</a>. */
  @Test void testSemiJoinRuleRight() {
    final String sql = "select dept.* from dept right join (\n"
        + "  select distinct deptno from emp\n"
        + "  where sal > 100) using (deptno)";
    sql(sql)
        .withPreRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_INTO_JOIN,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .withDecorrelate(true)
        .withTrim(true)
        .checkUnchanged();
  }

  /** Similar to {@link #testSemiJoinRuleRight()} but FULL. */
  @Test void testSemiJoinRuleFull() {
    final String sql = "select dept.* from dept full join (\n"
        + "  select distinct deptno from emp\n"
        + "  where sal > 100) using (deptno)";
    sql(sql)
        .withPreRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_INTO_JOIN,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .withDecorrelate(true)
        .withTrim(true)
        .checkUnchanged();
  }

  /** Similar to {@link #testSemiJoinRule()} but LEFT. */
  @Test void testSemiJoinRuleLeft() {
    final String sql = "select name from dept left join (\n"
        + "  select distinct deptno from emp\n"
        + "  where sal > 100) using (deptno)";
    sql(sql)
        .withPreRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_INTO_JOIN,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .withDecorrelate(true)
        .withTrim(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4941">[CALCITE-4941]
   * SemiJoinRule loses hints</a>. */
  @Test void testSemiJoinRuleWithHint() {
    final RelHint noHashJoinHint = RelHint.builder("no_hash_join").build();
    final Function<RelBuilder, RelNode> relFn = b -> {
      b.getCluster().setHintStrategies(
          HintStrategyTable.builder()
              .hintStrategy("no_hash_join", HintPredicates.JOIN)
              .build());
      return b
          .scan("DEPT")
          .scan("EMP")
          .project(b.field("DEPTNO"))
          .distinct()
          .join(
              JoinRelType.INNER,
              b.equals(
                  b.field(2, 0, "DEPTNO"),
                  b.field(2, 1, "DEPTNO"))).hints(noHashJoinHint)
          .project(b.field("DNAME"))
          .build();
    };

    // verify plan
    relFn(relFn)
        .withRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .check();

    // verify hint
    final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
    final RelNode input = relFn.apply(relBuilder);
    final HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.PROJECT_TO_SEMI_JOIN)
        .build();
    final HepPlanner hepPlanner = new HepPlanner(program);
    hepPlanner.setRoot(input);
    final RelNode output = hepPlanner.findBestExp();
    final Join join = (Join) output.getInput(0);
    assertTrue(join.getHints().contains(noHashJoinHint));
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-438">[CALCITE-438]
   * Push predicates through SemiJoin</a>. */
  @Test void testPushFilterThroughSemiJoin() {
    final String sql = "select * from (\n"
        + "  select * from dept where dept.deptno in (\n"
        + "    select emp.deptno from emp))R\n"
        + "where R.deptno <=10";
    sql(sql)
        .withExpand(true)
        .withDecorrelate(true)
        .withTrim(false)
        .withPreRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-571">[CALCITE-571]
   * ReduceExpressionsRule tries to reduce SemiJoin condition to non-equi
   * condition</a>. */
  @Test void testSemiJoinReduceConstants() {
    final String sql = "select e1.sal\n"
        + "from (select * from emp where deptno = 200) as e1\n"
        + "where e1.deptno in (\n"
        + "  select e2.deptno from emp e2 where e2.sal = 100)";
    sql(sql)
        .withDecorrelate(false)
        .withExpand(true)
        .withTrim(true)
        .withPreRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .withRule(CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testSemiJoinTrim() {
    final String sql = "select s.deptno\n"
        + "from (select *\n"
        + "  from dept\n"
        + "  where exists (\n"
        + "    select * from emp\n"
        + "    where emp.deptno = dept.deptno\n"
        + "    and emp.sal > 100)) s\n"
        + "join customer.account on s.deptno = account.acctno";

    final HepProgram program =
        HepProgram.builder()
            .addRuleInstance(CoreRules.FILTER_PROJECT_TRANSPOSE)
            .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
            .addRuleInstance(CoreRules.PROJECT_MERGE)
            .addRuleInstance(CoreRules.PROJECT_TO_SEMI_JOIN)
            .build();

    sql(sql)
        .withExpand(true)
        .withDecorrelate(true)
        .withPre(program)
        .withRule() // empty program
        .withAfter((fixture, r) ->
            fixture.tester.trimRelNode(
                fixture.factory.withSqlToRelConfig(c ->
                    c.withTrimUnusedFields(true)), r))
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5169">[CALCITE-5169]
   * 'xx < 1 OR xx > 1' cannot be simplified to 'xx <> 1'</a>.
   *
   * <p>{@code ename <> '' and ename <> '3'} should be simplified to Sarg.
   */
  @Test void testExpressionSimplification1() {
    final String sql = "select * from emp\n"
        + "where ename <> '' and ename <> '3'";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5169">[CALCITE-5169]
   * 'xx < 1 OR xx > 1' cannot be simplified to 'xx <> 1'</a>.
   *
   * <p>{@code (ename < '' or ename > '') and (ename < '3' or ename > '3')} should be
   * simplified to Sarg too.
   * The difference between this and {@link #testExpressionSimplification1()} is
   * that '<' and '<>' have different
   * {@link org.apache.calcite.sql.type.SqlOperandTypeChecker.Consistency} which
   * will lead to different type inference result for literals. These two tests
   * show that the simplification could handle this case.
   */
  @Test void testExpressionSimplification2() {
    final String sql = "select * from emp\n"
        + "where (ename < '' or ename > '') and (ename < '3' or ename > '3')";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceAverage() {
    final String sql = "select name, max(name), avg(deptno), min(name)\n"
        + "from sales.dept group by name";
    sql(sql).withRule(CoreRules.AGGREGATE_REDUCE_FUNCTIONS).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1621">[CALCITE-1621]
   * Adding a cast around the null literal in aggregate rules</a>. */
  @Test void testCastInAggregateReduceFunctions() {
    final String sql = "select name, stddev_pop(deptno), avg(deptno),\n"
        + "stddev_samp(deptno),var_pop(deptno), var_samp(deptno)\n"
        + "from sales.dept group by name";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7192">[CALCITE-7192]
   * AggregateReduceFunctionsRule lost FILTER condition in STDDEV/VAR function decomposition</a>. */
  @Test void testVarianceStddevWithFilter() {
    // Test to ensure FILTER conditions are correctly propagated to decomposed aggregates
    // for all functions that use the `reduceStddev` method
    final RelOptRule rule = AggregateReduceFunctionsRule.Config.DEFAULT
        .withOperandFor(LogicalAggregate.class)
        .withFunctionsToReduce(
            EnumSet.of(SqlKind.STDDEV_POP, SqlKind.STDDEV_SAMP,
                      SqlKind.VAR_POP, SqlKind.VAR_SAMP, SqlKind.AVG))
        .toRule();
    final String sql = "select name, "
        + "stddev_pop(deptno) filter (where deptno > 10), "
        + "stddev_samp(deptno) filter (where deptno > 20), "
        + "var_pop(deptno) filter (where deptno > 30), "
        + "var_samp(deptno) filter (where deptno > 40), "
        + "avg(deptno) filter (where deptno > 50)\n"
        + "from sales.dept group by name";
    sql(sql)
        .withRule(rule)
        .check();
  }

  @Test void testDistinctCountWithoutGroupBy() {
    final String sql = "select max(deptno), count(distinct ename)\n"
        + "from sales.emp";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.AGGREGATE_PROJECT_MERGE)
        .check();
  }

  @Test void testDistinctCount1() {
    final String sql = "select deptno, count(distinct ename)\n"
        + "from sales.emp group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.AGGREGATE_PROJECT_MERGE)
        .check();
  }

  @Test void testDistinctCount2() {
    final String sql = "select deptno, count(distinct ename), sum(sal)\n"
        + "from sales.emp group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.AGGREGATE_PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1293">[CALCITE-1293]
   * Bad code generated when argument to COUNT(DISTINCT) is a # GROUP BY
   * column</a>. */
  @Test void testDistinctCount3() {
    final String sql = "select count(distinct deptno), sum(sal)"
        + " from sales.emp group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES)
        .check();
  }

  /** Tests implementing multiple distinct count the old way, using a join. */
  @Test void testDistinctCountMultipleViaJoin() {
    final String sql = "select deptno, count(distinct ename),\n"
        + "  count(distinct job, ename),\n"
        + "  count(distinct deptno, job), sum(sal)\n"
        + "from sales.emp group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN,
            CoreRules.AGGREGATE_PROJECT_MERGE)
        .check();
  }

  /** Tests implementing multiple distinct count the new way, using GROUPING
   * SETS. */
  @Test void testDistinctCountMultiple() {
    final String sql = "select deptno, count(distinct ename),\n"
        + "  count(distinct job)\n"
        + "from sales.emp group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.AGGREGATE_PROJECT_MERGE)
        .check();
  }

  @Test void testDistinctCountMultipleNoGroup() {
    final String sql = "select count(distinct ename), count(distinct job)\n"
        + "from sales.emp";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.AGGREGATE_PROJECT_MERGE)
        .check();
  }

  @Test void testDistinctCountMixedJoin() {
    final String sql = "select deptno, count(distinct ename), count(distinct job, ename),\n"
        + "count(distinct deptno, job), sum(sal)\n"
        + "from sales.emp group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN,
            CoreRules.AGGREGATE_PROJECT_MERGE)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6332">
   * [CALCITE-6332] Optimization CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN
   * produces incorrect results for aggregates with groupSets</a>. */
  @Test void testIssue6332() {
    final String sql = "select count(distinct deptno) as cd, count(*) as c\n"
        + "from emp\n"
        + "group by cube(deptno)";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .checkUnchanged();
  }

  /** Test case for <a href="https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6349">
   * [CALCITE-6349] CoreRules.PROJECT_REDUCE_EXPRESSIONS crashes on expression
   * with ARRAY_REPEAT</a>. */
  @Test void testArrayRepeat() {
    final String sql = "select array_repeat(1, null)";
    sql(sql)
        .withFactory(
            t -> t.withOperatorTable(
                opTab -> SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.STANDARD, SqlLibrary.SPARK)))
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testDistinctCountMixed() {
    final String sql = "select deptno, count(distinct deptno, job) as cddj,\n"
        + "  sum(sal) as s\n"
        + "from sales.emp group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testDistinctCountMixed2() {
    final String sql = "select deptno, count(distinct ename) as cde,\n"
        + "count(distinct job, ename) as cdje,\n"
        + "count(distinct deptno, job) as cddj,\n"
        + "sum(sal) as s\n"
        + "from sales.emp group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testDistinctCountGroupingSets1() {
    final String sql = "select deptno, job, count(distinct ename)\n"
        + "from sales.emp group by rollup(deptno,job)";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6353">[CALCITE-6353]
   * Optimization CoreRules.PROJECT_REDUCE_EXPRESSIONS crashes
   * while optimizing ARRAY_CONCAT expression</a>. */
  @Test void testArrayConcat() {
    final String sql = "select array_concat(ARRAY [1, 2], ARRAY [3, 4])";
    sql(sql).withFactory(
        t -> t.withOperatorTable(
            opTab -> SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                SqlLibrary.STANDARD, SqlLibrary.BIG_QUERY)))
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testDistinctCountGroupingSets2() {
    final String sql = "select deptno, job, count(distinct ename), sum(sal)\n"
        + "from sales.emp group by rollup(deptno,job)";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5465">[CALCITE-5465]
   * Rule of AGGREGATE_EXPAND_DISTINCT_AGGREGATES produces an incorrect plan
   * when sql has distinct agg-call with rollup</a>. */
  @Test void testDistinctNonDistinctAggregatesWithGroupingSets() {
    final String sql = "SELECT deptno, COUNT(DISTINCT sal)\n"
        + "FROM emp\n"
        + "GROUP BY ROLLUP(deptno)";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5465">[CALCITE-5465]
   * Rule of AGGREGATE_EXPAND_DISTINCT_AGGREGATES produces an incorrect plan
   * when sql has distinct agg-call with rollup</a>. */
  @Test void testDistinctNonDistinctAggregatesWithGroupingSets2() {
    final String sql = "SELECT deptno, COUNT(DISTINCT sal), SUM(sal)\n"
        + "FROM emp\n"
        + "GROUP BY GROUPING SETS ((deptno), ())";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5465">[CALCITE-5465]
   * Rule of AGGREGATE_EXPAND_DISTINCT_AGGREGATES produces an incorrect plan
   * when sql has distinct agg-call with rollup</a>. */
  @Test void testDistinctNonDistinctAggregatesWithGroupingSets3() {
    final String sql = "SELECT deptno, COUNT(DISTINCT sal), SUM(DISTINCT sal), COUNT(*)\n"
        + "FROM emp\n"
        + "GROUP BY GROUPING SETS ((deptno), ())";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES)
        .check();
  }

  @Test void testDistinctNonDistinctAggregates() {
    final String sql = "select emp.empno, count(*), avg(distinct dept.deptno)\n"
        + "from sales.emp emp inner join sales.dept dept\n"
        + "on emp.deptno = dept.deptno\n"
        + "group by emp.empno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1621">[CALCITE-1621]
   * Adding a cast around the null literal in aggregate rules</a>. */
  @Test void testCastInAggregateExpandDistinctAggregatesRule() {
    final String sql = "select name, sum(distinct cn), sum(distinct sm)\n"
        + "from (\n"
        + "  select name, count(dept.deptno) as cn,sum(dept.deptno) as sm\n"
        + "  from sales.dept group by name)\n"
        + "group by name";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5884">[CALCITE-5884]
   * ARRAY_TO_STRING function should return NULL if its 'nullValue' argument is NULL</a>. */
  @Test void testArrayToString() {
    final String sql = "select array_to_string(array['1','2','3','4',NULL,'6'], ',', NULL)";
    // We expect the result to be NULL, since array_to_string returns NULL if
    // any argument is NULL.
    sql(sql).withFactory(
        t -> t.withOperatorTable(
            opTab -> SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                SqlLibrary.STANDARD, SqlLibrary.BIG_QUERY))) // for array_to_string function
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1558">[CALCITE-1558]
   * AggregateExpandDistinctAggregatesRule gets field mapping wrong if groupKey
   * is used in aggregate function</a>. */
  @Test void testDistinctNonDistinctAggregatesWithGrouping1() {
    final String sql = "SELECT deptno,\n"
        + "  SUM(deptno), SUM(DISTINCT sal), MAX(deptno), MAX(comm)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testDistinctNonDistinctAggregatesWithGrouping2() {
    final String sql = "SELECT deptno, COUNT(deptno), SUM(DISTINCT sal)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testDistinctNonDistinctTwoAggregatesWithGrouping() {
    final String sql = "SELECT deptno, SUM(comm), MIN(comm), SUM(DISTINCT sal)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testDistinctWithGrouping() {
    final String sql = "SELECT sal, SUM(comm), MIN(comm), SUM(DISTINCT sal)\n"
        + "FROM emp\n"
        + "GROUP BY sal";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testRemoveDistinctOnAgg() {
    final String sql = "SELECT empno, SUM(distinct sal), MIN(sal), "
        + "MIN(distinct sal), MAX(distinct sal), "
        + "bit_and(distinct sal), bit_or(sal), count(distinct sal) "
        + "from sales.emp group by empno, deptno\n";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_REMOVE,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testMultipleDistinctWithGrouping() {
    final String sql = "SELECT sal, SUM(comm), AVG(DISTINCT comm), SUM(DISTINCT sal)\n"
        + "FROM emp\n"
        + "GROUP BY sal";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testDistinctWithMultipleInputs() {
    final String sql = "SELECT deptno, SUM(comm), MIN(comm), COUNT(DISTINCT sal, comm)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testDistinctWithMultipleInputsAndGroupby() {
    final String sql = "SELECT deptno, SUM(comm), MIN(comm), COUNT(DISTINCT sal, deptno, comm)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testDistinctWithFilterWithoutGroupByUsingJoin() {
    final String sql = "SELECT SUM(comm), COUNT(DISTINCT sal) FILTER (WHERE sal > 1000)\n"
                       + "FROM emp";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testMultipleDistinctWithSameArgsDifferentFilterUsingJoin() {
    final String sql = "select deptno, "
                       + "count(distinct sal) FILTER (WHERE sal > 1000), "
                       + "count(distinct sal) FILTER (WHERE sal > 500) "
                       + "from sales.emp group by deptno";
    sql(sql)
        .withRule(
            CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN,
            CoreRules.AGGREGATE_PROJECT_MERGE
        )
        .check();
  }

  @Test void testDistinctWithFilterWithoutGroupBy() {
    final String sql = "SELECT SUM(comm), COUNT(DISTINCT sal) FILTER (WHERE sal > 1000)\n"
        + "FROM emp";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES)
        .check();
  }

  @Test void testDistinctWithDiffFiltersAndSameGroupSet() {
    final String sql = "SELECT COUNT(DISTINCT c) FILTER (WHERE d),\n"
        + "COUNT(DISTINCT d) FILTER (WHERE c)\n"
        + "FROM (select sal > 1000 is true as c, sal < 500 is true as d, comm from emp)";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5985">[CALCITE-5985]
   * FilterTableFunctionTransposeRule should not use "Logical" RelNodes</a>. */
  @Test void testFilterTableFunctionScanTranspose() {
    // "tfrt" table function is the one from MockSqlOperatorTable,
    // that implement TableFunctionReturnTypeInference.
    final String sql = "select * from table(tfrt(cursor(select name from dept)))"
        + " where name = '1'";

    sql(sql)
        .withRule(CoreRules.FILTER_TABLE_FUNCTION_TRANSPOSE)
        .check();
  }

  @Test void testDistinctWithFilterAndGroupBy() {
    final String sql = "SELECT deptno, SUM(comm), COUNT(DISTINCT sal) FILTER (WHERE sal > 1000)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES)
        .check();
  }

  /** Tests {@link AggregateExpandWithinDistinctRule}. The generated query
   * throws if arguments are not functionally dependent on the distinct key. */
  @Test void testWithinDistinct() {
    final String sql = "SELECT deptno, SUM(sal), SUM(sal) WITHIN DISTINCT (job)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT)
        .build();
    sql(sql).withProgram(program).check();
  }

  /** As {@link #testWithinDistinct()}, but the generated query does not throw
   * if arguments are not functionally dependent on the distinct key.
   *
   * @see AggregateExpandWithinDistinctRule.Config#throwIfNotUnique() */
  @Test void testWithinDistinctNoThrow() {
    final String sql = "SELECT deptno, SUM(sal), SUM(sal) WITHIN DISTINCT (job)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT
            .config.withThrowIfNotUnique(false).toRule())
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Tests {@link AggregateExpandWithinDistinctRule}. If all aggregate calls
   * have the same distinct keys, there is no need for multiple grouping
   * sets. */
  @Test void testWithinDistinctUniformDistinctKeys() {
    final String sql = "SELECT deptno,\n"
        + " SUM(sal) WITHIN DISTINCT (job),\n"
        + " AVG(comm) WITHIN DISTINCT (job)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT)
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Tests {@link AggregateExpandWithinDistinctRule}. If all aggregate calls
   * have the same distinct keys, and we're not checking for true uniqueness,
   * there is no need for filtering in the outer aggregate. */
  @Test void testWithinDistinctUniformDistinctKeysNoThrow() {
    final String sql = "SELECT deptno,\n"
        + " SUM(sal) WITHIN DISTINCT (job),\n"
        + " AVG(comm) WITHIN DISTINCT (job)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(
            CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT.config
                .withThrowIfNotUnique(false).toRule())
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Tests that {@link AggregateExpandWithinDistinctRule} treats
   * "COUNT(DISTINCT x)" as if it were "COUNT(x) WITHIN DISTINCT (x)". */
  @Test void testWithinDistinctCountDistinct() {
    final String sql = "SELECT deptno,\n"
        + "  SUM(sal) WITHIN DISTINCT (comm) AS ss_c,\n"
        + "  COUNT(DISTINCT job) cdj,\n"
        + "  COUNT(job) WITHIN DISTINCT (job) AS cj_j,\n"
        + "  COUNT(DISTINCT job) WITHIN DISTINCT (job) AS cdj_j,\n"
        + "  COUNT(DISTINCT job) FILTER (WHERE sal > 1000) AS cdj_filtered\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT
            .config.withThrowIfNotUnique(false).toRule())
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4726">[CALCITE-4726]
   * Support aggregate calls with a FILTER clause in
   * AggregateExpandWithinDistinctRule</a>.
   *
   * <p>Tests {@link AggregateExpandWithinDistinctRule} with different
   * distinct keys and different filters for each aggregate call. */
  @Test void testWithinDistinctFilteredAggs() {
    final String sql = "SELECT deptno,\n"
        + " SUM(sal) WITHIN DISTINCT (job) FILTER (WHERE comm > 10),\n"
        + " AVG(comm) WITHIN DISTINCT (sal) FILTER (WHERE ename LIKE '%ok%')\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT)
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5846">[CALCITE-5846]
   * Preserve FILTER clause for non-distinct aggregate calls with
   * AggregateExpandWithinDistinctRule</a>.
   *
   * <p>Tests {@link AggregateExpandWithinDistinctRule} with a non-distinct aggregate with a FILTER
   * clause and a distinct aggregate in the same query. */
  @Test void testWithinDistinctPreservesNonDistinctAggFilters() {
    final String sql = "SELECT deptno,\n"
        + " SUM(sal) FILTER (WHERE sal > 1000),\n"
        + " SUM(sal) WITHIN DISTINCT (job)\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT)
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6595">[CALCITE-6595]
   * Preserve collation for non-distinct aggregate calls with AggregateExpandWithinDistinctRule</a>.
   *
   * <p>Test that AggregateExpandWithinDistinctRule preserves collation on non-distinct aggregates
   * with a WITHIN GROUP clause in a query that also includes a distinct aggregate. */
  @Test void testWithinDistinctPreservesNonDistinctCollation() {
    final String sql = "SELECT SUM(sal) WITHIN DISTINCT (job),\n"
        + "LISTAGG(ename, '; ') WITHIN GROUP (ORDER BY sal DESC)\n"
        + " FROM Emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT)
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Tests {@link AggregateExpandWithinDistinctRule}. Includes multiple
   * different filters for the aggregate calls, and all aggregate calls have the
   * same distinct keys, so there is no need to filter based on
   * {@code GROUPING()}. */
  @Test void testWithinDistinctFilteredAggsUniformDistinctKeys() {
    final String sql = "SELECT deptno,\n"
        + " SUM(sal) WITHIN DISTINCT (job) FILTER (WHERE comm > 10),\n"
        + " AVG(comm) WITHIN DISTINCT (job) FILTER (WHERE ename LIKE '%ok%')\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT)
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Tests {@link AggregateExpandWithinDistinctRule}. Includes multiple
   * different filters for the aggregate calls, and all aggregate calls have the
   * same distinct keys, so there is no need to filter based on
   * {@code GROUPING()}. Does <em>not</em> throw if not unique. */
  @Test void testWithinDistinctFilteredAggsUniformDistinctKeysNoThrow() {
    final String sql = "SELECT deptno,\n"
        + " SUM(sal) WITHIN DISTINCT (job) FILTER (WHERE comm > 10),\n"
        + " AVG(comm) WITHIN DISTINCT (job) FILTER (WHERE ename LIKE '%ok%')\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(
            CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT.config
                .withThrowIfNotUnique(false).toRule())
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Tests {@link AggregateExpandWithinDistinctRule}. Includes multiple
   * identical filters for the aggregate calls. The filters should be
   * re-used. */
  @Test void testWithinDistinctFilteredAggsSameFilter() {
    final String sql = "SELECT deptno,\n"
        + " SUM(sal) WITHIN DISTINCT (job) FILTER (WHERE ename LIKE '%ok%'),\n"
        + " AVG(comm) WITHIN DISTINCT (sal) FILTER (WHERE ename LIKE '%ok%')\n"
        + "FROM emp\n"
        + "GROUP BY deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_REDUCE_FUNCTIONS)
        .addRuleInstance(CoreRules.AGGREGATE_EXPAND_WITHIN_DISTINCT)
        .build();
    sql(sql).withProgram(program).check();
  }

  @Test void testPushProjectPastFilter() {
    final String sql = "select empno + deptno from emp where sal = 10 * comm\n"
        + "and upper(ename) = 'FOO'";
    sql(sql).withRule(CoreRules.PROJECT_FILTER_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1778">[CALCITE-1778]
   * Query with "WHERE CASE" throws AssertionError "Cast for just nullability
   * not allowed"</a>. */
  @Test void testPushProjectPastFilter2() {
    final String sql = "select count(*)\n"
        + "from emp\n"
        + "where case when mgr < 10 then true else false end";
    sql(sql).withRule(CoreRules.PROJECT_FILTER_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3975">[CALCITE-3975]
   * ProjectFilterTransposeRule should succeed for project that happens to
   * reference all input columns</a>. */
  @Test void testPushProjectPastFilter3() {
    checkPushProjectPastFilter3(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .checkUnchanged();
  }

  /** As {@link #testPushProjectPastFilter3()} but pushes down project and
   * filter expressions whole. */
  @Test void testPushProjectPastFilter3b() {
    checkPushProjectPastFilter3(CoreRules.PROJECT_FILTER_TRANSPOSE_WHOLE_EXPRESSIONS)
        .check();
  }

  /** As {@link #testPushProjectPastFilter3()} but pushes down project
   * expressions whole. */
  @Test void testPushProjectPastFilter3c() {
    checkPushProjectPastFilter3(
        CoreRules.PROJECT_FILTER_TRANSPOSE_WHOLE_PROJECT_EXPRESSIONS)
        .check();
  }

  RelOptFixture checkPushProjectPastFilter3(ProjectFilterTransposeRule rule) {
    final String sql = "select empno + deptno as x, ename, job, mgr,\n"
        + "  hiredate, sal, comm, slacker\n"
        + "from emp\n"
        + "where sal = 10 * comm\n"
        + "and upper(ename) = 'FOO'";
    return sql(sql).withRule(rule);
  }

  @Test void testPushProjectPastJoin() {
    final String sql = "select e.sal + b.comm from emp e inner join bonus b\n"
        + "on e.ename = b.ename and e.deptno = 10";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3004">[CALCITE-3004]
   * Should not push over past union but its operands can since setop
   * will affect row count</a>. */
  @Test void testProjectSetOpTranspose() {
    final String sql = "select job, sum(sal + 100) over (partition by deptno) from\n"
        + "(select * from emp e1 union all select * from emp e2)";
    sql(sql).withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE).check();
  }

  @Test void testProjectCorrelateTransposeDynamic() {
    ProjectCorrelateTransposeRule customPCTrans =
        ProjectCorrelateTransposeRule.Config.DEFAULT
            .withPreserveExprCondition(RelOptRulesTest::skipItem)
            .toRule();

    String sql = "select t1.c_nationkey, t2.a as fake_col2 "
        + "from SALES.CUSTOMER as t1, "
        + "unnest(t1.fake_col) as t2(a)";
    sql(sql)
        .withDynamicTable()
        .withRule(customPCTrans)
        .checkUnchanged();
  }

  @Test void testProjectCorrelateTransposeRuleLeftCorrelate() {
    final String sql = "SELECT e1.empno\n"
        + "FROM emp e1 "
        + "where exists (select empno, deptno from dept d2 where e1.deptno = d2.deptno)";
    sql(sql)
        .withDecorrelate(false)
        .withExpand(true)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.PROJECT_FILTER_TRANSPOSE,
            CoreRules.PROJECT_CORRELATE_TRANSPOSE)
        .check();
  }

  @Test void testProjectCorrelateTransposeRuleSemiCorrelate() {
    checkProjectCorrelateTransposeRuleSemiOrAntiCorrelate(JoinRelType.SEMI);
  }

  @Test void testProjectCorrelateTransposeRuleAntiCorrelate() {
    checkProjectCorrelateTransposeRuleSemiOrAntiCorrelate(JoinRelType.ANTI);
  }

  private void checkProjectCorrelateTransposeRuleSemiOrAntiCorrelate(JoinRelType type) {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode left = b
          .values(new String[]{"f", "f2"}, "1", "2").build();

      CorrelationId correlationId = new CorrelationId(0);
      RexNode rexCorrel =
          b.getRexBuilder().makeCorrel(
              left.getRowType(),
              correlationId);

      RelNode right = b
          .values(new String[]{"f3", "f4"}, "1", "2")
          .project(b.field(0),
              b.getRexBuilder().makeFieldAccess(rexCorrel, 0)).build();
      LogicalCorrelate correlate =
          new LogicalCorrelate(left.getCluster(), left.getTraitSet(),
              ImmutableList.of(), left, right, correlationId,
              ImmutableBitSet.of(0), type);

      b.push(correlate);
      return b.project(b.field(0))
          .build();
    };

    relFn(relFn).withRule(CoreRules.PROJECT_CORRELATE_TRANSPOSE).check();
  }

  @Test void testProjectCorrelateTransposeWithExprCond() {
    ProjectCorrelateTransposeRule customPCTrans =
        ProjectCorrelateTransposeRule.Config.DEFAULT
            .withPreserveExprCondition(RelOptRulesTest::skipItem)
            .toRule();

    final String sql = "select t1.name, t2.ename\n"
        + "from DEPT_NESTED as t1,\n"
        + "unnest(t1.employees) as t2";
    sql(sql).withRule(customPCTrans).check();
  }

  @Test void testSwapOuterJoinFieldAccess() {
    HepProgram preProgram = new HepProgramBuilder()
        .addMatchLimit(1)
        .addRuleInstance(CoreRules.JOIN_PROJECT_LEFT_TRANSPOSE_INCLUDE_OUTER)
        .addRuleInstance(CoreRules.PROJECT_MERGE)
        .build();
    final HepProgram program = new HepProgramBuilder()
        .addMatchLimit(1)
        .addRuleInstance(CoreRules.JOIN_COMMUTE_OUTER)
        .addRuleInstance(CoreRules.PROJECT_MERGE)
        .build();
    final String sql = "select t1.name, e.ename\n"
        + "from DEPT_NESTED as t1 left outer join sales.emp e\n"
        + " on t1.skill.type = e.job";
    sql(sql).withPre(preProgram).withProgram(program).check();
  }

  @Test void testProjectCorrelateTranspose() {
    ProjectCorrelateTransposeRule customPCTrans =
        ProjectCorrelateTransposeRule.Config.DEFAULT
            .withPreserveExprCondition(expr -> true)
            .toRule();
    final String sql = "select t1.name, t2.ename\n"
        + "from DEPT_NESTED as t1,\n"
        + "unnest(t1.employees) as t2";
    sql(sql).withRule(customPCTrans).check();
  }

  /** As {@link #testProjectSetOpTranspose()};
   * should not push over past correlate but its operands can since correlate
   * will affect row count. */
  @Test void testProjectCorrelateTransposeWithOver() {
    final String sql = "select sum(t1.deptno + 1) over (partition by t1.name),\n"
        + "count(t2.empno) over ()\n"
        + "from DEPT_NESTED as t1,\n"
        + "unnest(t1.employees) as t2";
    sql(sql).withRule(CoreRules.PROJECT_CORRELATE_TRANSPOSE).check();
  }

  /** Tests that the default instance of {@link FilterProjectTransposeRule}
   * does not push a Filter that contains a correlating variable.
   *
   * @see #testFilterProjectTranspose() */
  @Test void testFilterProjectTransposePreventedByCorrelation() {
    final String sql = "SELECT e.empno\n"
        + "FROM emp as e\n"
        + "WHERE exists (\n"
        + "  SELECT *\n"
        + "  FROM (\n"
        + "    SELECT deptno * 2 AS twiceDeptno\n"
        + "    FROM dept) AS d\n"
        + "  WHERE e.deptno = d.twiceDeptno)";
    sql(sql)
        .withDecorrelate(false)
        .withExpand(true)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE)
        .checkUnchanged();
  }

  /** Tests a variant of {@link FilterProjectTransposeRule}
   * that pushes a Filter that contains a correlating variable. */
  @Test void testFilterProjectTranspose() {
    final String sql = "SELECT e.empno\n"
        + "FROM emp as e\n"
        + "WHERE exists (\n"
        + "  SELECT *\n"
        + "  FROM (\n"
        + "    SELECT deptno * 2 AS twiceDeptno\n"
        + "    FROM dept) AS d\n"
        + "  WHERE e.deptno = d.twiceDeptno)";
    final FilterProjectTransposeRule filterProjectTransposeRule =
        CoreRules.FILTER_PROJECT_TRANSPOSE.config
            .withOperandSupplier(b0 ->
                b0.operand(Filter.class).predicate(filter -> true)
                    .oneInput(b1 ->
                        b1.operand(Project.class).predicate(project -> true)
                            .anyInputs()))
            .as(FilterProjectTransposeRule.Config.class)
            .withCopyFilter(true)
            .withCopyProject(true)
            .toRule();
    sql(sql)
        .withDecorrelate(false)
        .withExpand(true)
        .withRule(filterProjectTransposeRule)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6513">[CALCITE-6513]
   * FilterProjectTransposeRule may cause OOM when Project expressions are
   * complex</a>. */
  @Test void testFilterProjectTransposeBloat() {
    String sql =
        "SELECT x1 from\n"
        + "    (SELECT 'L1' || x0  || x0 || x0 || x0 as x1 from\n"
        + "        (SELECT 'L0' || ENAME || ENAME || ENAME || ENAME as x0 from emp) t1) t2\n"
        + "WHERE x1 = 'Something'";

    final FilterProjectTransposeRule filterProjectTransposeRule =
        CoreRules.FILTER_PROJECT_TRANSPOSE.config
            .withOperandSupplier(b0 ->
                b0.operand(Filter.class).predicate(filter -> true)
                    .oneInput(b1 ->
                        b1.operand(Project.class).predicate(project -> true)
                            .anyInputs()))
            .as(FilterProjectTransposeRule.Config.class)
            .withCopyFilter(true)
            .withCopyProject(true)
            .withBloat(3)
            .toRule();
    sql(sql)
        .withRelBuilderConfig(config -> config.withBloat(3))
        .withDecorrelate(false)
        .withExpand(true)
        .withRule(filterProjectTransposeRule)
        .check();
  }

  private static final String NOT_STRONG_EXPR =
      "case when e.sal < 11 then 11 else -1 * e.sal end";

  private static final String STRONG_EXPR =
      "case when e.sal < 11 then -1 * e.sal else e.sal end";

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1753">[CALCITE-1753]
   * PushProjector should only preserve expressions if the expression is strong
   * when pushing into the nullable-side of outer join</a>. */
  @Test void testPushProjectPastInnerJoin() {
    final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
        + "from emp e inner join bonus b on e.ename = b.ename\n"
        + "group by " + NOT_STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastInnerJoinStrong() {
    final String sql = "select count(*), " + STRONG_EXPR + "\n"
        + "from emp e inner join bonus b on e.ename = b.ename\n"
        + "group by " + STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7065">[CALCITE-7065]
   * CoreRules.PROJECT_REDUCE_EXPRESSIONS crashes when applied to a lambda</a>. */
  @Test void testReduceLambda() {
    final String sql = "select \"EXISTS\"(ARRAY[1], x -> true)";
    sql(sql)
        .withFactory(f ->
            f.withOperatorTable(opTab ->
                SqlValidatorTest.operatorTableFor(SqlLibrary.SPARK)))
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS).checkUnchanged();
  }

  @Test void testPushProjectPastLeftJoin() {
    final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
        + "from emp e left outer join bonus b on e.ename = b.ename\n"
        + "group by case when e.sal < 11 then 11 else -1 * e.sal end";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastLeftJoinSwap() {
    final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
        + "from bonus b left outer join emp e on e.ename = b.ename\n"
        + "group by " + NOT_STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastLeftJoinSwapStrong() {
    final String sql = "select count(*), " + STRONG_EXPR + "\n"
        + "from bonus b left outer join emp e on e.ename = b.ename\n"
        + "group by " + STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastRightJoin() {
    final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
        + "from emp e right outer join bonus b on e.ename = b.ename\n"
        + "group by " + NOT_STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastRightJoinStrong() {
    final String sql = "select count(*),\n"
        + " case when e.sal < 11 then -1 * e.sal else e.sal end\n"
        + "from emp e right outer join bonus b on e.ename = b.ename\n"
        + "group by case when e.sal < 11 then -1 * e.sal else e.sal end";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastRightJoinSwap() {
    final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
        + "from bonus b right outer join emp e on e.ename = b.ename\n"
        + "group by " + NOT_STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastRightJoinSwapStrong() {
    final String sql = "select count(*), " + STRONG_EXPR + "\n"
        + "from bonus b right outer join emp e on e.ename = b.ename\n"
        + "group by " + STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastFullJoin() {
    final String sql = "select count(*), " + NOT_STRONG_EXPR + "\n"
        + "from emp e full outer join bonus b on e.ename = b.ename\n"
        + "group by " + NOT_STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastFullJoinStrong() {
    final String sql = "select count(*), " + STRONG_EXPR + "\n"
        + "from emp e full outer join bonus b on e.ename = b.ename\n"
        + "group by " + STRONG_EXPR;
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2343">[CALCITE-2343]
   * Should not push over whose columns are all from left child past join since
   * join will affect row count</a>. */
  @Test void testPushProjectWithOverPastJoin1() {
    final String sql = "select e.sal + b.comm,\n"
        + "count(e.empno) over (partition by e.deptno)\n"
        + "from emp e join bonus b\n"
        + "on e.ename = b.ename and e.deptno = 10";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  /** As {@link #testPushProjectWithOverPastJoin1()};
   * should not push over whose columns are all from right child past join since
   * join will affect row count. */
  @Test void testPushProjectWithOverPastJoin2() {
    final String sql = "select e.sal + b.comm,\n"
        + "count(b.sal) over (partition by b.job)\n"
        + "from emp e join bonus b\n"
        + "on e.ename = b.ename and e.deptno = 10";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  /** As {@link #testPushProjectWithOverPastJoin2()};
   * should not push over past join but should push the operands of over past
   * join. */
  @Test void testPushProjectWithOverPastJoin3() {
    final String sql = "select e.sal + b.comm,\n"
        + "sum(b.sal + b.sal + 100) over (partition by b.job)\n"
        + "from emp e join bonus b\n"
        + "on e.ename = b.ename and e.deptno = 10";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4982">[CALCITE-4982]
   * NonNull field shouldn't be pushed down into leaf of outer-join
   * in 'ProjectJoinTransposeRule'</a>. */
  @Test void testPushProjectPastOutJoinWithCastNonNullExpr() {
    final String sql = "select e.empno + 1 as c1, coalesce(d.name, b.job, '') as c2\n"
        + "from emp e\n"
        + "left join bonus b on e.ename = b.ename\n"
        + "left join dept d on e.deptno = d.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_TRANSPOSE).check();
  }

  @Test void testPushProjectPastSetOp() {
    final String sql = "select sal from\n"
        + "(select * from emp e1 union all select * from emp e2)";
    sql(sql).withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE).check();
  }

  @Test void testPushJoinThroughUnionOnLeft() {
    final String sql = "select r1.sal from\n"
        + "(select * from emp e1 union all select * from emp e2) r1,\n"
        + "emp r2";
    sql(sql).withRule(CoreRules.JOIN_LEFT_UNION_TRANSPOSE).check();
  }

  @Test void testPushJoinThroughUnionOnRight() {
    final String sql = "select r1.sal from\n"
        + "emp r1,\n"
        + "(select * from emp e1 union all select * from emp e2) r2";
    sql(sql).withRule(CoreRules.JOIN_RIGHT_UNION_TRANSPOSE).check();
  }

  @Test void testPushJoinThroughUnionOnRightDoesNotMatchSemiJoin() {
    // build a rel equivalent to sql:
    // select r1.sal from
    // emp r1 where r1.deptno in
    //  (select deptno from dept d1 where deptno < 10
    //  union all
    //  select deptno from dept d2 where deptno > 20)
    checkPushJoinThroughUnionOnRightDoesNotMatchSemiOrAntiJoin(JoinRelType.SEMI);
  }

  @Test void testPushJoinThroughUnionOnRightDoesNotMatchAntiJoin() {
    // build a rel equivalent to sql:
    // select r1.sal from
    // emp r1 where r1.deptno not in
    //  (select deptno from dept d1 where deptno < 10
    //  union all
    //  select deptno from dept d2 where deptno > 20)
    checkPushJoinThroughUnionOnRightDoesNotMatchSemiOrAntiJoin(JoinRelType.ANTI);
  }

  private void checkPushJoinThroughUnionOnRightDoesNotMatchSemiOrAntiJoin(JoinRelType type) {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode left = b.scan("EMP").build();
      RelNode right = b
          .scan("DEPT")
          .filter(b.lessThan(b.field("DEPTNO"), b.literal(10)))
          .project(b.field("DEPTNO"))
          .scan("DEPT")
          .filter(b.greaterThan(b.field("DEPTNO"), b.literal(20)))
          .project(b.field("DEPTNO"))
          .union(true)
          .build();
      return b.push(left).push(right)
          .join(type,
              b.equals(b.field(2, 0, "DEPTNO"),
                  b.field(2, 1, "DEPTNO")))
          .project(b.field("SAL"))
          .build();
    };
    relFn(relFn).withRule(CoreRules.JOIN_RIGHT_UNION_TRANSPOSE).checkUnchanged();
  }

  @Test void testMergeFilterWithJoinCondition() {
    final String sql = "select d.name as dname,e.ename as ename\n"
        + " from emp e inner join dept d\n"
        + " on e.deptno=d.deptno\n"
        + " where d.name='Propane'";
    sql(sql)
        .withRule(CoreRules.JOIN_EXTRACT_FILTER,
            CoreRules.FILTER_TO_CALC,
            CoreRules.PROJECT_TO_CALC,
            CoreRules.CALC_MERGE)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5073">[CALCITE-5073]
   * JoinConditionPushRule cannot infer 'LHS.C1 = LHS.C2' from
   * 'LHS.C1 = RHS.C1 AND LHS.C2 = RHS.C1'</a>.
   */
  @Test void testJoinConditionPushdown1() {
    final String sql = "select *\n"
        + "from emp e1, emp e2, dept d2\n"
        + "where e1.deptno = d2.deptno and e2.deptno = d2.deptno";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.PROJECT_MERGE,
            CoreRules.FILTER_PROJECT_TRANSPOSE)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5073">[CALCITE-5073]
   * JoinConditionPushRule cannot infer 'LHS.C1 = LHS.C2' from
   * 'LHS.C1 = RHS.C1 AND LHS.C2 = RHS.C1'</a>.
   */
  @Test void testJoinConditionPushdown2() {
    final String sql = "select *\n"
        + "from emp e, dept d\n"
        + "where e.deptno = d.deptno and e.empno = d.deptno";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.PROJECT_MERGE,
            CoreRules.FILTER_PROJECT_TRANSPOSE)
        .check();
  }

  /** Similar to {@link #testJoinConditionPushdown1()} but full join
   * from which more equality conditions can not be inferred. */
  @Test void testJoinConditionPushdown3() {
    final String sql = "select *\n"
        + "from emp e full join dept d\n"
        + "on e.deptno = d.deptno and e.empno = d.deptno";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.PROJECT_MERGE,
            CoreRules.FILTER_PROJECT_TRANSPOSE)
        .checkUnchanged();
  }

  /** Similar to {@link #testJoinConditionPushdown1()} but semi join
   * from which more equality conditions can be inferred. */
  @Test void testJoinConditionPushdown4() {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode left = b.scan("EMP")
          .project(
              b.field("DEPTNO"),
              b.field("ENAME"))
          .build();
      RelNode right = b.scan("DEPT")
          .project(
              b.field("DEPTNO"),
              b.field("DNAME"))
          .build();

      b.push(left).push(right);

      RexInputRef ref1 = b.field(2, 0, "DEPTNO");
      RexInputRef ref2 = b.field(2, 1, "DEPTNO");
      RexInputRef ref3 = b.field(2, 1, "DNAME");

      RexCall cond1 = (RexCall) b.equals(ref1, ref2);
      RexCall cond2 = (RexCall) b.equals(ref1, ref3);

      RexNode cond = b.and(cond1, cond2);
      return b.semiJoin(cond)
          .project(b.field(0))
          .build();
    };

    relFn(relFn)
        .withRule(
            CoreRules.JOIN_PUSH_EXPRESSIONS,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.SEMI_JOIN_PROJECT_TRANSPOSE,
            CoreRules.JOIN_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Similar to {@link #testJoinConditionPushdown1()} but left join
   * from which more equality conditions can not be inferred. */
  @Test void testJoinConditionPushdown5() {
    final String sql = "select *\n"
        + "from emp e left join dept d\n"
        + "on e.deptno = d.deptno and e.empno = d.deptno";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.PROJECT_MERGE,
            CoreRules.FILTER_PROJECT_TRANSPOSE)
        .checkUnchanged();
  }

  /** Similar to {@link #testJoinConditionPushdown1()} but right join
   * from which more equality conditions can be inferred. */
  @Test void testJoinConditionPushdown6() {
    final String sql = "select *\n"
        + "from emp e right join dept d\n"
        + "on e.deptno = d.deptno and e.empno = d.deptno";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.PROJECT_MERGE,
            CoreRules.FILTER_PROJECT_TRANSPOSE)
        .check();
  }

  /** Tests that filters are combined if they are identical. */
  @Test void testMergeFilter() {
    final String sql = "select name from (\n"
        + "  select *\n"
        + "  from dept\n"
        + "  where deptno = 10)\n"
        + "where deptno = 10\n";
    sql(sql)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_MERGE)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6639">[CALCITE-6639]
   * Optimization that pulls up predicates causes ASOF JOIN validation failures</a>. */
  @Test void testAsofOpt() {
    final String sql = "SELECT *\n"
        + "FROM (VALUES (NULL, 0), (1, NULL), (1, 0), (1, 1), (1, 2), "
        + "(1, 3), (1, 4), (2, 3), (3, 4)) AS t1(k, t)\n"
        + "ASOF JOIN (VALUES (1, NULL), (1, 2), (1, 3), (2, 10), (2, 0)) AS t2(k, t)\n"
        + "MATCH_CONDITION t2.t < t1.t\n"
        + "ON t1.k = t2.k\n";
    sql(sql).withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7273">[CALCITE-7273]
   *  CoreRules.JOIN_REDUCE_EXPRESSIONS throws when applied to an ASOF JOIN</a>. */
  @Test void testAsofOptJoin() {
    // Had to use ROW values to cause the optimization rule to match
    final String sql = "SELECT *\n"
        + "FROM (VALUES (NULL, ROW(0, 1)), (1, ROW(0, 2))) AS t1(k, t)\n"
        + "ASOF JOIN (VALUES (2, ROW(0, 3))) AS t2(k, t)\n"
        + "MATCH_CONDITION t2.t < t1.t\n"
        + "ON t1.k = t2.k\n";
    sql(sql).withRule(CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  /** Tests to see if the final branch of union is missed. */
  @Test void testUnionMergeRule() {
    final String sql = "select * from (\n"
        + "select * from (\n"
        + "  select name, deptno from dept\n"
        + "  union all\n"
        + "  select name, deptno from\n"
        + "  (\n"
        + "    select name, deptno, count(1) from dept group by name, deptno\n"
        + "    union all\n"
        + "    select name, deptno, count(1) from dept group by name, deptno\n"
        + "  ) subq\n"
        + ") a\n"
        + "union all\n"
        + "select name, deptno from dept\n"
        + ") aa\n";
    sql(sql)
        .withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE,
            CoreRules.PROJECT_REMOVE,
            CoreRules.UNION_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6953">[CALCITE-6953]
   * Extend UnionEliminatorRule to support Intersect and Minus</a>. */
  @Test void testUnionEliminatorRuleUnion() {
    final Function<RelBuilder, RelNode> relFn =
        b -> LogicalUnion.create(ImmutableList.of(b.scan("DEPT").build()), true);

    relFn(relFn)
        .withRule(CoreRules.UNION_REMOVE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6953">[CALCITE-6953]
   * Extend UnionEliminatorRule to support Intersect and Minus</a>. */
  @Test void testUnionEliminatorRuleIntersect() {
    final Function<RelBuilder, RelNode> relFn =
        b -> LogicalIntersect.create(ImmutableList.of(b.scan("DEPT").build()), true);

    relFn(relFn)
        .withRule(CoreRules.INTERSECT_REMOVE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6953">[CALCITE-6953]
   * Extend UnionEliminatorRule to support Intersect and Minus</a>. */
  @Test void testUnionEliminatorRuleMinus() {
    final Function<RelBuilder, RelNode> relFn =
        b -> LogicalMinus.create(ImmutableList.of(b.scan("DEPT").build()), true);

    relFn(relFn)
        .withRule(CoreRules.MINUS_REMOVE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6617">[CALCITE-6617]
   * TypeCoercion is not applied correctly to comparisons</a>. */
  @Test void testRand() {
    final String sql = "SELECT * FROM (SELECT 1, ROUND(RAND()) AS A)\n"
        + "WHERE A BETWEEN 1 AND 10 OR A IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)";
    sql(sql).withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6481">[CALCITE-6481]
   * Optimize 'VALUES...UNION...VALUES' to a single 'VALUES' the IN-list contains CAST
   * and it is converted to VALUES</a>. */
  @Test void testUnionToValuesByInList() {
    final String sql = ""
        + "\n"
        + "with t1(a, y) as (select * from (values (1, 2), (3, null), (7369, null), (7499, 30), (null, 20), (null, 5)) as t1)\n"
        + "select *\n"
        + "from t1\n"
        + "where (t1.a, t1.y) in ((1, 2), (3, null), (7369, null), (7499, 30), (null, 20), (null, 5))";
    sql(sql)
        .withExpand(true)
        .withRelBuilderConfig(b -> b.withSimplifyValues(false))
        .withRule(CoreRules.PROJECT_VALUES_MERGE,
            CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByInList2() {
    final String sql = ""
        + "\n"
        + "with t1(a, y) as (select * from (values (1, 2), (3, null), (7369, null), (7499, 30), (null, 20), (null, 5)) as t1)\n"
        + "select *\n"
        + "from t1\n"
        + "where (t1.a, t1.y) in ((cast(1.1 as int), 2), (3, null), (7369, null), (7499, 30), (null, cast(20.2 as int)), (null, 5))";
    sql(sql)
        .withExpand(true)
        .withRelBuilderConfig(b -> b.withSimplifyValues(false))
        .withRule(CoreRules.PROJECT_VALUES_MERGE,
            CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByInList3() {
    final String sql = "select * from dept where deptno in (12, 34, cast(56.4 as int))";
    sql(sql)
        .withExpand(true)
        .withRelBuilderConfig(b -> b.withSimplifyValues(false))
        .withRule(CoreRules.PROJECT_VALUES_MERGE,
            CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByInList4() {
    final String sql = "select * from dept where deptno in (12, 34, cast(56.4 as double))";
    sql(sql)
        .withExpand(true)
        .withRelBuilderConfig(b -> b.withSimplifyValues(false))
        .withRule(CoreRules.PROJECT_VALUES_MERGE,
            CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByInList5() {
    final String sql = "select deptno in (12, 34, cast(56.4 as double)) from dept";
    sql(sql)
        .withExpand(true)
        .withRelBuilderConfig(b -> b.withSimplifyValues(false))
        .withRule(CoreRules.PROJECT_VALUES_MERGE,
            CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByUnion() {
    final String sql = "select *\n"
        + "from (values (1, 'a'), (2, 'b')) as t(x, y)\n"
        + "union\n"
        + "select *\n"
        + "from (values (1, 'a'), (2, 'b'), (1, 'b'), (2, 'c'), (2, 'c')) as t(x, y)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REMOVE, CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByUnion2() {
    final String sql = "select *\n"
        + "from (values ('a'), ('b')) as t(x)\n"
        + "union\n"
        + "select *\n"
        + "from (values ('a'), ('b'), ('b'), ('c'), ('c')) as t(y)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REMOVE, CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByUnion3() {
    final String sql = "select *\n"
        + "from (values (5.0)) as t(x)\n"
        + "union\n"
        + "select *\n"
        + "from (values (5)) as t(y)";
    sql(sql)
        .withRule(
            CoreRules.PROJECT_REMOVE,
            CoreRules.PROJECT_VALUES_MERGE,
            CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByUnion4() {
    final String sql = "select *\n"
        + "from (values (cast(5.0 as int))) as t(x)\n"
        + "union\n"
        + "select *\n"
        + "from (values (5)) as t(y)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REMOVE, CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByUnionAll() {
    final String sql = "select *\n"
        + "from (values (1, 'a'), (2, 'b')) as t(x, y)\n"
        + "union all\n"
        + "select *\n"
        + "from (values (1, 'a'), (2, 'b'), (1, 'b'), (2, 'c'), (2, 'c')) as t(x, y)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REMOVE, CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByUnionAll2() {
    final String sql = "select *\n"
        + "from (values ('a'), ('b')) as t(x)\n"
        + "union all\n"
        + "select *\n"
        + "from (values ('a'), ('b'), ('b'), ('c'), ('c')) as t(y)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REMOVE, CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByUnionAll3() {
    final String sql = "select *\n"
        + "from (values (5.0)) as t(x)\n"
        + "union all\n"
        + "select *\n"
        + "from (values (5)) as t(y)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REMOVE,
            CoreRules.PROJECT_VALUES_MERGE,
            CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testUnionToValuesByUnionAll4() {
    final String sql = "select *\n"
        + "from (values (cast(5.0 as int))) as t(x)\n"
        + "union all\n"
        + "select *\n"
        + "from (values (5)) as t(y)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REMOVE, CoreRules.UNION_TO_VALUES)
        .withInSubQueryThreshold(0)
        .check();
  }

  @Test void testMinusMergeRule() {
    final String sql = "select * from (\n"
        + "select * from (\n"
        + "  select name, deptno from\n"
        + "  (\n"
        + "    select name, deptno, count(1) from dept group by name, deptno\n"
        + "    except all\n"
        + "    select name, deptno, 1 from dept\n"
        + "  ) subq\n"
        + "  except all\n"
        + "  select name, deptno from\n"
        + "  (\n"
        + "    select name, deptno, 1 from dept\n"
        + "    except all\n"
        + "    select name, deptno, count(1) from dept group by name, deptno\n"
        + "  ) subq2\n"
        + ") a\n"
        + "except all\n"
        + "select name, deptno from dept\n"
        + ") aa\n";
    sql(sql)
        .withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE,
            CoreRules.PROJECT_REMOVE,
            CoreRules.MINUS_MERGE)
        .check();
  }

  /** Tests that a filters is combined are combined if they are identical,
   * even if one of them originates in an ON clause of a JOIN. */
  @Test void testMergeJoinFilter() {
    final String sql = "select * from (\n"
        + "  select d.deptno, e.ename\n"
        + "  from emp as e\n"
        + "  join dept as d\n"
        + "  on e.deptno = d.deptno\n"
        + "  and d.deptno = 10)\n"
        + "where deptno = 10\n";
    sql(sql)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_MERGE,
            CoreRules.FILTER_INTO_JOIN)
        .check();
  }

  /** Tests {@link UnionMergeRule}, which merges 2 {@link Union} operators into
   * a single {@code Union} with 3 inputs. */
  @Test void testMergeUnionAll() {
    final String sql = "select * from emp where deptno = 10\n"
        + "union all\n"
        + "select * from emp where deptno = 20\n"
        + "union all\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.UNION_MERGE)
        .check();
  }

  /** Tests {@link UnionMergeRule}, which merges 2 {@link Union}
   * {@code DISTINCT} (not {@code ALL}) operators into a single
   * {@code Union} with 3 inputs. */
  @Test void testMergeUnionDistinct() {
    final String sql = "select * from emp where deptno = 10\n"
        + "union distinct\n"
        + "select * from emp where deptno = 20\n"
        + "union\n" // same as 'union distinct'
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.UNION_MERGE)
        .check();
  }

  /** Tests that {@link UnionMergeRule} does nothing if its arguments have
   * different {@code ALL} settings. */
  @Test void testMergeUnionMixed() {
    final String sql = "select * from emp where deptno = 10\n"
        + "union\n"
        + "select * from emp where deptno = 20\n"
        + "union all\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.UNION_MERGE)
        .checkUnchanged();
  }

  /** Tests that {@link UnionMergeRule} converts all inputs to DISTINCT
   * if the top one is DISTINCT.
   * (Since UNION is left-associative, the "top one" is the rightmost.) */
  @Test void testMergeUnionMixed2() {
    final String sql = "select * from emp where deptno = 10\n"
        + "union all\n"
        + "select * from emp where deptno = 20\n"
        + "union\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.UNION_MERGE)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-2067">
   * [CALCITE-2067] RexLiteral cannot represent accurately floating point values,
   * including NaN, Infinity</a>. */
  @Test public void testDoubleReduction() {
    // Without the fix for CALCITE-2067 the result returned below is
    // 1008618.49.  Ironically, that result is more accurate; however
    // it is not the result returned by the pow() function, which is
    // 1008618.4899999999
    final String sql = "SELECT power(1004.3, 2)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-2067">
   * [CALCITE-2067] RexLiteral cannot represent accurately floating point values,
   * including NaN, Infinity</a>. */
  @Test public void testDoubleReduction2() {
    // Without the fix for CALCITE-2067 the following expression is not
    // reduced to Infinity, since Infinity cannot be represented
    // as a BigDecimal value.
    final String sql2 = "SELECT 1.0 / 0.0e0";
    sql(sql2)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Tests that {@link UnionMergeRule} does nothing if its arguments have
   * are different set operators, {@link Union} and {@link Intersect}. */
  @Test void testMergeSetOpMixed() {
    final String sql = "select * from emp where deptno = 10\n"
        + "union\n"
        + "select * from emp where deptno = 20\n"
        + "intersect\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.UNION_MERGE,
            CoreRules.INTERSECT_MERGE)
        .checkUnchanged();
  }

  /** Tests {@link CoreRules#INTERSECT_MERGE}, which merges 2
   * {@link Intersect} operators into a single {@code Intersect} with 3
   * inputs. */
  @Test void testMergeIntersect() {
    final String sql = "select * from emp where deptno = 10\n"
        + "intersect\n"
        + "select * from emp where deptno = 20\n"
        + "intersect\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.INTERSECT_MERGE)
        .check();
  }

  /** Tests {@link org.apache.calcite.rel.rules.IntersectToDistinctRule},
   * which rewrites an {@link Intersect} operator with 3 inputs. */
  @Test void testIntersectToDistinct() {
    final String sql = "select * from emp where deptno = 10\n"
        + "intersect\n"
        + "select * from emp where deptno = 20\n"
        + "intersect\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.INTERSECT_MERGE,
            CoreRules.INTERSECT_TO_DISTINCT)
        .check();
  }

  /** Tests {@link org.apache.calcite.rel.rules.IntersectToDistinctRule} with
   * the option to not perform aggregation pushdown (see
   * {@link CoreRules#INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN}).
   * which rewrites an {@link Intersect} operator with 3 inputs. */
  @Test void testIntersectToDistinctNoAggregatePushdown() {
    final String sql = "select * from emp where deptno = 10\n"
        + "intersect\n"
        + "select * from emp where deptno = 20\n"
        + "intersect\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.INTERSECT_MERGE,
            CoreRules.INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN)
        .check();
  }

  /** Tests that {@link org.apache.calcite.rel.rules.IntersectToDistinctRule}
   * correctly ignores an {@code INTERSECT ALL}. It can only handle
   * {@code INTERSECT DISTINCT}. */
  @Test void testIntersectToDistinctAll() {
    final String sql = "select * from emp where deptno = 10\n"
        + "intersect\n"
        + "select * from emp where deptno = 20\n"
        + "intersect all\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.INTERSECT_MERGE,
            CoreRules.INTERSECT_TO_DISTINCT)
        .check();
  }

  /** Tests {@link org.apache.calcite.rel.rules.IntersectToDistinctRule} with
   * the option to not perform aggregation pushdown (see
   * {@link CoreRules#INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN}).
   * correctly ignores an {@code INTERSECT ALL}. It can only handle
   * {@code INTERSECT DISTINCT}. */
  @Test void testIntersectToDistinctAllNoAggregatePushdown() {
    final String sql = "select * from emp where deptno = 10\n"
        + "intersect\n"
        + "select * from emp where deptno = 20\n"
        + "intersect all\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.INTERSECT_MERGE,
            CoreRules.INTERSECT_TO_DISTINCT_NO_AGGREGATE_PUSHDOWN)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6880">
   * [CALCITE-6880] Implement IntersectToSemiJoinRule</a>. */
  @Test void testIntersectToSemiJoin() {
    final String sql = "select ename from emp where deptno = 10\n"
        + "intersect\n"
        + "select ename from emp where deptno = 20\n";
    sql(sql).withRule(CoreRules.INTERSECT_TO_SEMI_JOIN)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7000">[CALCITE-7000]
   * Extend IntersectToSemiJoinRule to support n-way inputs</a>. */
  @Test void testIntersectToSemiJoin2() {
    final String sql = "select ename from emp where deptno = 10\n"
        + "intersect\n"
        + "select deptno from emp where ename in ('a', 'b')\n"
        + "intersect\n"
        + "select ename from empnullables\n";
    sql(sql).withPreRule(CoreRules.INTERSECT_MERGE)
        .withRule(CoreRules.INTERSECT_TO_SEMI_JOIN)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6880">
   * [CALCITE-6880] Implement IntersectToSemiJoinRule</a>. */
  @Test void testIntersectToSemiJoinMultiCol() {
    final String sql = "select deptno, ename from emp where deptno = 10\n"
        + "intersect\n"
        + "select deptno, ename from emp where deptno = 20\n";
    sql(sql).withRule(CoreRules.INTERSECT_TO_SEMI_JOIN)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6880">
   * [CALCITE-6880] Implement IntersectToSemiJoinRule</a>. */
  @Test void testIntersectToSemiJoinAll() {
    final String sql = "select ename from emp where deptno = 10\n"
        + "intersect\n"
        + "select ename from emp where deptno = 20\n"
        + "intersect all\n"
        + "select ename from emp where deptno = 30\n";
    sql(sql).withRule(CoreRules.INTERSECT_TO_SEMI_JOIN)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6880">
   * [CALCITE-6880] Implement IntersectToSemiJoinRule</a>. */
  @Test void testIntersectToSemiJoinCoercion() {
    final String sql = "select ename from emp where deptno = 10\n"
        + "intersect\n"
        + "select job from emp where deptno = 20\n";
    sql(sql).withRule(CoreRules.INTERSECT_TO_SEMI_JOIN)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-6948">[CALCITE-6948]
   * Implement MinusToAntiJoinRule</a>. */
  @Test void testMinusToAntiJoinRule() {
    final String sql = "select ename from emp where deptno = 10\n"
        + "except\n"
        + "select ename from emp where deptno = 20\n";
    sql(sql).withRule(CoreRules.MINUS_TO_ANTI_JOIN)
        .check();
  }

  @Test void testMinusToAntiJoinRuleMultiInputs() {
    final String sql = "select ename from emp where deptno = 10\n"
        + "except\n"
        + "select deptno from emp where ename in ('a', 'b')\n"
        + "except\n"
        + "select ename from empnullables\n";
    sql(sql).withPreRule(CoreRules.MINUS_MERGE)
        .withRule(CoreRules.MINUS_TO_ANTI_JOIN)
        .check();
  }

  /** Tests {@link CoreRules#MINUS_MERGE}, which merges 2
   * {@link Minus} operators into a single {@code Minus} with 3
   * inputs. */
  @Test void testMergeMinus() {
    final String sql = "select * from emp where deptno = 10\n"
        + "except\n"
        + "select * from emp where deptno = 20\n"
        + "except\n"
        + "select * from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.MINUS_MERGE)
        .check();
  }

  /** Tests {@link org.apache.calcite.rel.rules.MinusToDistinctRule},
   * which rewrites an {@link Minus} operator with 3 inputs. */
  @Test void testMinusToDistinct() {
    final String sql = "select EMPNO, ENAME, JOB from emp where deptno = 10\n"
        + "except\n"
        + "select EMPNO, ENAME, JOB from emp where deptno = 20\n"
        + "except\n"
        + "select EMPNO, ENAME, JOB from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.MINUS_MERGE, CoreRules.MINUS_TO_DISTINCT, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Tests {@link org.apache.calcite.rel.rules.MinusToDistinctRule},
   *  correctly ignores an {@code EXCEPT ALL}. It can only handle
   *  {@code EXCEPT DISTINCT}. */
  @Test void testMinusToDistinctAll() {
    final String sql = "select EMPNO, ENAME, JOB from emp where deptno = 10\n"
        + "except\n"
        + "select EMPNO, ENAME, JOB from emp where deptno = 20\n"
        + "except all\n"
        + "select EMPNO, ENAME, JOB from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.MINUS_MERGE, CoreRules.MINUS_TO_DISTINCT, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Tests {@link org.apache.calcite.rel.rules.MinusToDistinctRule},
   *  correctly ignores an {@code EXCEPT ALL}. It can only handle
   *  {@code EXCEPT DISTINCT}. */
  @Test void testMinusToDistinctAllAndDistinct() {
    final String sql = "select EMPNO, ENAME, JOB from emp where deptno = 10\n"
        + "except all\n"
        + "select EMPNO, ENAME, JOB from emp where deptno = 20\n"
        + "except\n"
        + "select EMPNO, ENAME, JOB from emp where deptno = 30\n";
    sql(sql)
        .withRule(CoreRules.MINUS_TO_DISTINCT, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Tests {@link CoreRules#MINUS_MERGE}
   * does not merge {@code Minus(a, Minus(b, c))}
   * into {@code Minus(a, b, c)}, which would be incorrect. */
  @Test void testMergeMinusRightDeep() {
    final String sql = "select * from emp where deptno = 10\n"
        + "except\n"
        + "select * from (\n"
        + "  select * from emp where deptno = 20\n"
        + "  except\n"
        + "  select * from emp where deptno = 30)";
    sql(sql)
        .withRule(CoreRules.MINUS_MERGE)
        .checkUnchanged();
  }

  @Test void testHeterogeneousConversion() {
    // This one tests the planner's ability to correctly
    // apply different converters on top of a common
    // sub-expression.  The common sub-expression is the
    // reference to the table sales.emps.  On top of that
    // are two projections, unioned at the top.  For one
    // of the projections, transfer it to calc, for the other,
    // keep it unchanged.
    HepProgram program = new HepProgramBuilder()
        // Control the calc conversion.
        .addMatchLimit(1)
        .addRuleInstance(CoreRules.PROJECT_TO_CALC)
        .build();

    final String sql = "select upper(ename) from emp union all\n"
        + "select lower(ename) from emp";
    sql(sql).withProgram(program).check();
  }

  @Test void testPushSemiJoinPastJoinRuleLeft() {
    // tests the case where the semijoin is pushed to the left
    final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
        + "where e1.deptno = d.deptno and e1.empno = e2.empno";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN,
            CoreRules.SEMI_JOIN_JOIN_TRANSPOSE)
        .check();
  }

  @Test void testPushSemiJoinPastJoinRuleRight() {
    // tests the case where the semijoin is pushed to the right
    final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
        + "where e1.deptno = d.deptno and d.deptno = e2.deptno";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN,
            CoreRules.SEMI_JOIN_JOIN_TRANSPOSE)
        .check();
  }

  @Test void testPushSemiJoinPastJoinRuleNotHappensJoinKeysDifferentOrigin() {
    // tests the case where the semijoin is not pushed because it uses join keys from both tables
    // of the bottom join.
    final String sql = "select e.ename from emp e, dept d, bonus b\n"
        + "where e.deptno = d.deptno and e.ename = b.ename and d.name = b.job";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN,
            CoreRules.SEMI_JOIN_JOIN_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5952">[CALCITE-5952]
   * SemiJoinJoinTransposeRule should check if JoinType supports pushing predicates
   * into its inputs</a>. */
  @Test void testPushSemiJoinToLeftJoinLeftInput() {
    // tests the case that semijoin can be pushed to the left input of the left join
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("DEPT")
        .scan("EMP")
        .join(JoinRelType.LEFT,
            b.equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO"))
        )
        .scan("BONUS")
        .semiJoin(
            b.equals(
                b.field(2, 0, "DNAME"),
                b.field(2, 1, "JOB")))
        .build();
    relFn(relFn).withRule(CoreRules.SEMI_JOIN_JOIN_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5952">[CALCITE-5952]
   * SemiJoinJoinTransposeRule should check if JoinType supports pushing predicates
   * into its inputs</a>. */
  @Test void testPushSemiJoinToRightJoinRightInput() {
    // tests the case that semijoin can be pushed to the right input of the right join
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .scan("DEPT")
        .join(JoinRelType.RIGHT,
            b.equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO"))
        )
        .scan("BONUS")
        .semiJoin(
            b.equals(
                b.field(2, 0, "DNAME"),
                b.field(2, 1, "JOB")))
        .build();
    relFn(relFn).withRule(CoreRules.SEMI_JOIN_JOIN_TRANSPOSE).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5952">[CALCITE-5952]
   * SemiJoinJoinTransposeRule should check if JoinType supports pushing predicates
   * into its inputs</a>. */
  @Test void testCanNotPushSemiJoinToLeftJoinRightInput() {
    // tests the case that semijoin cannot be pushed to the right input of the left join
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .scan("DEPT")
        .join(JoinRelType.LEFT,
            b.equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO"))
        )
        .scan("BONUS")
        .semiJoin(
            b.equals(
                b.field(2, 0, "DNAME"),
                b.field(2, 1, "JOB")))
        .build();
    relFn(relFn).withRule(CoreRules.SEMI_JOIN_JOIN_TRANSPOSE).checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5952">[CALCITE-5952]
   * SemiJoinJoinTransposeRule should check if JoinType supports pushing predicates
   * into its inputs</a>. */
  @Test void testCanNotPushSemiJoinToRightJoinLeftInput() {
    // tests the case that semijoin cannot be pushed to the left input of the right join
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("DEPT")
        .scan("EMP")
        .join(JoinRelType.RIGHT,
            b.equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO"))
        )
        .scan("BONUS")
        .semiJoin(
            b.equals(
                b.field(2, 0, "DNAME"),
                b.field(2, 1, "JOB")))
        .build();
    relFn(relFn).withRule(CoreRules.SEMI_JOIN_JOIN_TRANSPOSE).checkUnchanged();
  }

  @Test void testPushSemiJoinPastFilter() {
    final String sql = "select e.ename from emp e, dept d\n"
        + "where e.deptno = d.deptno and e.ename = 'foo'";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN,
            CoreRules.SEMI_JOIN_FILTER_TRANSPOSE)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-5289">
   * [CALCITE-5289] Assertion failure in MultiJoinOptimizeBushyRule</a>. */
  @Test void testBushyJoinRule() {
    final String sql = "select emp.ename from emp LEFT JOIN emp AS emp1 on emp.ename = emp1.ename";
    sql(sql).withPreRule(CoreRules.JOIN_TO_MULTI_JOIN)
        .withRule(CoreRules.MULTI_JOIN_OPTIMIZE_BUSHY)
        .checkUnchanged();
  }

  @Test void testConvertMultiJoinRule() {
    final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
        + "where e1.deptno = d.deptno and d.deptno = e2.deptno";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.JOIN_TO_MULTI_JOIN)
        .build();
    sql(sql).withProgram(program).check();
  }

  @Test void testManyFiltersOnTopOfMultiJoinShouldCollapse() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.JOIN_TO_MULTI_JOIN)
        .addRuleCollection(
            Arrays.asList(CoreRules.FILTER_MULTI_JOIN_MERGE,
                CoreRules.PROJECT_MULTI_JOIN_MERGE))
        .build();
    final String sql = "select * from (select * from emp e1 left outer join dept d\n"
        + "on e1.deptno = d.deptno\n"
        + "where d.deptno > 3) where ename LIKE 'bar'";
    sql(sql).withProgram(program).check();
  }

  @Test void testReduceConstants() {
    // NOTE jvs 27-May-2006: among other things, this verifies
    // intentionally different treatment for identical coalesce expression
    // in select and where.

    // There is "CAST(2 AS INTEGER)" in the plan because 2 has type "INTEGER NOT
    // NULL" and we need "INTEGER".
    final String sql = "select"
        + " 1+2, d.deptno+(3+4), (5+6)+d.deptno, cast(null as integer),"
        + " coalesce(2,null), row(7+8)"
        + " from dept d inner join emp e"
        + " on d.deptno = e.deptno + (5-5)"
        + " where d.deptno=(7+8) and d.deptno=(8+7) and d.deptno=coalesce(2,null)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .withRelBuilderSimplify(false)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7070">[CALCITE-7070]
   * FILTER_REDUCE_EXPRESSIONS crashes on expression BETWEEN ( NULL) AND X</a>. */
  @Test void testFilterReduceCrash() {
    final String sql = "SELECT + 54 FROM emp WHERE NOT CAST ( CAST(empno AS DOUBLE) AS INTEGER ) "
        + "NOT BETWEEN ( NULL ) AND 89";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-570">[CALCITE-570]
   * ReduceExpressionsRule throws "duplicate key" exception</a>. */
  @Test void testReduceConstantsDup() {
    final String sql = "select d.deptno"
        + " from dept d"
        + " where d.deptno=7 and d.deptno=8";
    sql(sql).withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-935">[CALCITE-935]
   * Improve how ReduceExpressionsRule handles duplicate constraints</a>. */
  @Test void testReduceConstantsDup2() {
    final String sql = "select *\n"
        + "from emp\n"
        + "where deptno=7 and deptno=8\n"
        + "and empno = 10 and mgr is null and empno = 10";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
   * Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
  @Test void testReduceConstantsDup3() {
    final String sql = "select d.deptno"
        + " from dept d"
        + " where d.deptno<>7 or d.deptno<>8";
    sql(sql).withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
   * Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
  @Test void testReduceConstantsDup3Null() {
    final String sql = "select e.empno"
        + " from emp e"
        + " where e.mgr<>7 or e.mgr<>8";
    sql(sql).withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
   * Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
  @Test void testReduceConstantsDupNot() {
    final String sql = "select d.deptno"
        + " from dept d"
        + " where not(d.deptno=7 and d.deptno=8)";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
   * Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
  @Test void testReduceConstantsDupNotNull() {
    final String sql = "select e.empno"
        + " from emp e"
        + " where not(e.mgr=7 and e.mgr=8)";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
   * Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
  @Test void testReduceConstantsDupNot2() {
    final String sql = "select d.deptno"
        + " from dept d"
        + " where not(d.deptno=7 and d.name='foo' and d.deptno=8)";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3198">[CALCITE-3198]
   * Enhance RexSimplify to handle (x&lt;&gt;a or x&lt;&gt;b)</a>. */
  @Test void testReduceConstantsDupNot2Null() {
    final String sql = "select e.empno"
        + " from emp e"
        + " where not(e.mgr=7 and e.deptno=8 and e.mgr=8)";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7140">[CALCITE-7140]
   * Improve constant reduction of expressions containing SqlRowOperator</a>. */
  @Test void testReduceConstantsWithRow() {
    final String sql = "SELECT 1+1, ARRAY[1+1, 2+2], ROW('a', 3+3), MAP['a'||'b', 6+6],\n"
        + "ROW(1+2, ARRAY[1+2, 2+3], ROW('a', 3+4), MAP['ab'||'cd', 6+7]),\n"
        + "ARRAY[2*3, 2, 1],\n"
        + "ARRAY[ROW(4*2, 'a'), ROW(4+4, 'b')],\n"
        + "ARRAY[MAP['abc'||'def', 8+8], MAP['ghi'||'jkl', 9+9]],\n"
        + "ARRAY[ROW(1+2, ARRAY[1+1, 2+2], ROW('a', 3+3), MAP['x'||'y', 8+8])],\n"
        + "CARDINALITY(ARRAY[2*3, 2*2, 1*4]),\n"
        + "CARDINALITY(ARRAY[ROW(4*3, 'm'), ROW(4+6, 'b')]),\n"
        + "CARDINALITY(ARRAY[MAP['ac'||'bd', 1+8], MAP['gi'||'jl', 1+9]]),\n"
        + "CARDINALITY(ARRAY[ROW(1+7, ARRAY[1+4, 4+2], ROW('t', 5+5), MAP['y'||'z', 9+9])])";
    sql(sql).withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS).check();
  }

  @Test void testPullNull() {
    final String sql = "select *\n"
        + "from emp\n"
        + "where deptno=7\n"
        + "and empno = 10 and mgr is null and empno = 10";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testOrAlwaysTrue() {
    final String sql = "select * from EMPNULLABLES_20\n"
        + "where sal is null or sal is not null";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testOrAlwaysTrue2() {
    final String sql = "select * from EMPNULLABLES_20\n"
        + "where sal is not null or sal is null";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceConstants2() {
    final String sql = "select p1 is not distinct from p0\n"
        + "from (values (2, cast(null as integer))) as t(p0, p1)";
    sql(sql)
        .withRelBuilderConfig(b -> b.withSimplifyValues(false))
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  @Test void testReduceConstants3() {
    final String sql = "select e.mgr is not distinct from f.mgr "
        + "from emp e join emp f on (e.mgr=f.mgr) where e.mgr is null";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-902">[CALCITE-902]
   * Match nullability when reducing expressions in a Project</a>. */
  @Test void testReduceConstantsProjectNullable() {
    final String sql = "select mgr from emp where mgr=10";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  // see HIVE-9645
  @Test void testReduceConstantsNullEqualsOne() {
    final String sql = "select count(1) from emp where cast(null as integer) = 1";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  // see HIVE-9644
  @Test void testReduceConstantsCaseEquals() {
    final String sql = "select count(1) from emp\n"
        + "where case deptno\n"
        + "  when 20 then 2\n"
        + "  when 10 then 1\n"
        + "  else 3 end = 1";
    // Equivalent to 'deptno = 10'
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceConstantsCaseEquals2() {
    final String sql = "select count(1) from emp\n"
        + "where case deptno\n"
        + "  when 20 then 2\n"
        + "  when 10 then 1\n"
        + "  else cast(null as integer) end = 1";

    // Equivalent to 'case when deptno = 20 then false
    //                     when deptno = 10 then true
    //                     else null end'
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceConstantsCaseEquals3() {
    final String sql = "select count(1) from emp\n"
        + "where case deptno\n"
        + "  when 30 then 1\n"
        + "  when 20 then 2\n"
        + "  when 10 then 1\n"
        + "  when 30 then 111\n"
        + "  else 0 end = 1";

    // Equivalent to 'deptno = 30 or deptno = 10'
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testSkipReduceConstantsCaseEquals() {
    final String sql = "select * from emp e1, emp e2\n"
        + "where coalesce(e1.mgr, -1) = coalesce(e2.mgr, -1)";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_INTO_JOIN)
        .check();
  }

  @Test void testReduceConstantsEliminatesFilter() {
    final String sql = "select * from (values (1,2)) where 1 + 2 > 3 + CAST(NULL AS INTEGER)";

    // WHERE NULL is the same as WHERE FALSE, so get empty result
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1860">[CALCITE-1860]
   * Duplicate null predicates cause NullPointerException in RexUtil</a>. */
  @Test void testReduceConstantsNull() {
    final String sql = "select * from (\n"
        + "  select *\n"
        + "  from (\n"
        + "    select cast(null as integer) as n\n"
        + "    from emp)\n"
        + "  where n is null and n is null)\n"
        + "where n is null";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-566">[CALCITE-566]
   * ReduceExpressionsRule requires planner to have an Executor</a>. */
  @Test void testReduceConstantsRequiresExecutor() {
    // Rule should not fire, but there should be no NPE
    // Create a new planner instance, so we can remove its executor without
    // breaking other tests.
    final RelOptPlanner planner = new MockRelOptPlanner(Contexts.empty());
    final String sql =
        "select * from (values (1,2)) where 1 + 2 > 3 + CAST(NULL AS INTEGER)";
    sql(sql)
        .withFactory(t -> t.withPlannerFactory(context -> planner))
        .withBefore((fixture, r) -> {
          // Remove the executor
          assertThat(r.getCluster().getPlanner(), sameInstance(planner));
          r.getCluster().getPlanner().setExecutor(null);
          return r;
        })
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testAlreadyFalseEliminatesFilter() {
    final String sql = "select * from (values (1,2)) where false";

    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceConstantsCalc() {
    // This reduction does not work using
    // ReduceExpressionsRule.PROJECT_INSTANCE or FILTER_INSTANCE,
    // only CALC_INSTANCE, because we need to pull the project expression
    //    upper('table')
    // into the condition
    //    upper('table') = 'TABLE'
    // and reduce it to TRUE. Only in the Calc are projects and conditions
    // combined.
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.FILTER_PROJECT_TRANSPOSE)
        .addRuleInstance(CoreRules.FILTER_SET_OP_TRANSPOSE)
        .addRuleInstance(CoreRules.FILTER_TO_CALC)
        .addRuleInstance(CoreRules.PROJECT_TO_CALC)
        .addRuleInstance(CoreRules.CALC_MERGE)
        .addRuleInstance(CoreRules.CALC_REDUCE_EXPRESSIONS)

            // the hard part is done... a few more rule calls to clean up
        .addRuleInstance(PruneEmptyRules.UNION_INSTANCE)
        .addRuleInstance(CoreRules.PROJECT_TO_CALC)
        .addRuleInstance(CoreRules.CALC_MERGE)
        .addRuleInstance(CoreRules.CALC_REDUCE_EXPRESSIONS)
        .build();

    // Result should be same as typing
    //  SELECT * FROM (VALUES ('TABLE        ', 'T')) AS T(U, S)
    final String sql = "select * from (\n"
        + "  select upper(substring(x FROM 1 FOR 2) || substring(x FROM 3)) as u,\n"
        + "      substring(x FROM 1 FOR 1) as s\n"
        + "  from (\n"
        + "    select 'table' as x from (values (true))\n"
        + "    union\n"
        + "    select 'view' from (values (true))\n"
        + "    union\n"
        + "    select 'foreign table' from (values (true))\n"
        + "  )\n"
        + ") where u = 'TABLE'";
    sql(sql)
        .withRelBuilderConfig(c -> c.withSimplifyValues(false))
        .withProgram(program).check();
  }

  @Test void testReducingConstantsInferredFromCorrelate() {
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .build();

    final String sql = "SELECT ename,\n"
        + "  empno,\n"
        + "  T.empno_r,\n"
        + "  CASE\n"
        + "    WHEN __source__type__ = 'bounded'\n"
        + "      THEN 1\n"
        + "    ELSE 2\n"
        + "    END AS type\n"
        + "FROM (\n"
        + "  SELECT ename,\n"
        + "    empno,\n"
        + "    'bounded' AS __source__type__\n"
        + "  FROM emp\n"
        + "  ) a,\n"
        + "  lateral TABLE (ramp(empno)) AS T(empno_r)";
    sql(sql)
        .withRelBuilderConfig(c -> c.withSimplifyValues(false))
        .withProgram(program).check();
  }


  @Test void testRemoveSemiJoin() {
    final String sql = "select e.ename from emp e, dept d\n"
        + "where e.deptno = d.deptno";
    sql(sql)
        .withPreRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN)
        .withRule(CoreRules.SEMI_JOIN_REMOVE)
        .check();
  }

  @Test void testRemoveSemiJoinWithFilter() {
    final String sql = "select e.ename from emp e, dept d\n"
        + "where e.deptno = d.deptno and e.ename = 'foo'";
    sql(sql)
        .withPreRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN,
            CoreRules.SEMI_JOIN_FILTER_TRANSPOSE)
        .withRule(CoreRules.SEMI_JOIN_REMOVE)
        .check();
  }

  @Test void testRemoveSemiJoinRight() {
    final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
        + "where e1.deptno = d.deptno and d.deptno = e2.deptno";
    sql(sql)
        .withPreRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN,
            CoreRules.SEMI_JOIN_JOIN_TRANSPOSE)
        .withRule(CoreRules.SEMI_JOIN_REMOVE)
        .check();
  }

  @Test void testRemoveSemiJoinRightWithFilter() {
    final String sql = "select e1.ename from emp e1, dept d, emp e2\n"
        + "where e1.deptno = d.deptno and d.deptno = e2.deptno\n"
        + "and d.name = 'foo'";
    sql(sql)
        .withPreRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN,
            CoreRules.SEMI_JOIN_JOIN_TRANSPOSE,
            CoreRules.SEMI_JOIN_FILTER_TRANSPOSE)
        .withRule(CoreRules.SEMI_JOIN_REMOVE)
        .check();
  }

  /** Creates an environment for testing multi-join queries. */
  private RelOptFixture multiJoin(String query) {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.PROJECT_REMOVE)
        .addRuleInstance(CoreRules.JOIN_TO_MULTI_JOIN)
        .build();
    return sql(query)
        .withCatalogReaderFactory((typeFactory, caseSensitive) ->
            new MockCatalogReader(typeFactory, caseSensitive) {
              @Override public MockCatalogReader init() {
                // CREATE SCHEMA abc;
                // CREATE TABLE a(a INT);
                // ...
                // CREATE TABLE j(j INT);
                MockSchema schema = new MockSchema("SALES");
                registerSchema(schema);
                final RelDataType intType =
                    typeFactory.createSqlType(SqlTypeName.INTEGER);
                for (int i = 0; i < 10; i++) {
                  String t = String.valueOf((char) ('A' + i));
                  MockTable table = MockTable.create(this, schema, t, false, 100);
                  table.addColumn(t, intType);
                  registerTable(table);
                }
                return this;
              }
              // CHECKSTYLE: IGNORE 1
            }.init())
        .withProgram(program);
  }

  @Test void testConvertMultiJoinRuleOuterJoins() {
    final String sql = "select * from "
        + "    (select * from "
        + "        (select * from "
        + "            (select * from A right outer join B on a = b) "
        + "            left outer join "
        + "            (select * from C full outer join D on c = d)"
        + "            on a = c and b = d) "
        + "        right outer join "
        + "        (select * from "
        + "            (select * from E full outer join F on e = f) "
        + "            right outer join "
        + "            (select * from G left outer join H on g = h) "
        + "            on e = g and f = h) "
        + "        on a = e and b = f and c = g and d = h) "
        + "    inner join "
        + "    (select * from I inner join J on i = j) "
        + "    on a = i and h = j";
    multiJoin(sql).check();
  }

  @Test void testConvertMultiJoinRuleOuterJoins2() {
    // in (A right join B) join C, pushing C is not allowed;
    // therefore there should be 2 MultiJoin
    multiJoin("select * from A right join B on a = b join C on b = c")
        .check();
  }

  @Test void testConvertMultiJoinRuleOuterJoins3() {
    // in (A join B) left join C, pushing C is allowed;
    // therefore there should be 1 MultiJoin
    multiJoin("select * from A join B on a = b left join C on b = c")
        .check();
  }

  @Test void testConvertMultiJoinRuleOuterJoins4() {
    // in (A join B) right join C, pushing C is not allowed;
    // therefore there should be 2 MultiJoin
    multiJoin("select * from A join B on a = b right join C on b = c")
        .check();
  }

  @Test void testPushSemiJoinPastProject() {
    final String sql = "select e.* from\n"
        + "(select ename, trim(job), sal * 2, deptno from emp) e, dept d\n"
        + "where e.deptno = d.deptno";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_ADD_REDUNDANT_SEMI_JOIN,
            CoreRules.SEMI_JOIN_PROJECT_TRANSPOSE)
        .check();
  }

  @Test void testReduceValuesUnderFilter() {
    // Plan should be same as for
    // select a, b from (values (10,'x')) as t(a, b)");
    final String sql = "select a, b from (values (10, 'x'), (20, 'y')) as t(a, b) where a < 15";
    sql(sql)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_VALUES_MERGE)
        .check();
  }

  @Test void testReduceValuesUnderProject() {
    // Plan should be same as for
    // select a, b as x from (values (11), (23)) as t(x)");
    final String sql = "select a + b from (values (10, 1), (20, 3)) as t(a, b)";
    sql(sql)
        .withRule(CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_VALUES_MERGE)
        .check();
  }

  @Test void testReduceValuesUnderProjectFilter() {
    // Plan should be same as for
    // select * from (values (11, 1, 10), (23, 3, 20)) as t(x, b, a)");
    final String sql = "select a + b as x, b, a\n"
        + "from (values (10, 1), (30, 7), (20, 3)) as t(a, b)\n"
        + "where a - b < 21";
    sql(sql).withRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
        CoreRules.PROJECT_MERGE,
        CoreRules.PROJECT_FILTER_VALUES_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1439">[CALCITE-1439]
   * Handling errors during constant reduction</a>. */
  @Test void testReduceCase() {
    final String sql = "select\n"
        + "  case when false then cast(2.1 as float)\n"
        + "   else cast(1 as integer) end as newcol\n"
        + "from emp";
    sql(sql).withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .withRelBuilderSimplify(false)
        .check();
  }

  private void checkReduceNullableToNotNull(ReduceExpressionsRule<?> rule) {
    final String sql = "select\n"
        + "  empno + case when 'a' = 'a' then 1 else null end as newcol\n"
        + "from emp";
    sql(sql).withRule(rule)
        .withRelBuilderSimplify(false)
        .check();
  }

  /** Test case that reduces a nullable expression to a NOT NULL literal that
   * is cast to nullable. */
  @Test void testReduceNullableToNotNull() {
    checkReduceNullableToNotNull(CoreRules.PROJECT_REDUCE_EXPRESSIONS);
  }

  /** Test case that reduces a nullable expression to a NOT NULL literal. */
  @Test void testReduceNullableToNotNull2() {
    final ProjectReduceExpressionsRule rule =
        CoreRules.PROJECT_REDUCE_EXPRESSIONS.config
            .withOperandFor(LogicalProject.class)
            .withMatchNullability(false)
            .as(ProjectReduceExpressionsRule.ProjectReduceExpressionsRuleConfig.class)
            .toRule();
    checkReduceNullableToNotNull(rule);
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2736">[CALCITE-2736]
   * ReduceExpressionsRule never reduces dynamic expressions but this should be
   * configurable</a>. Tests that a dynamic function (USER) is reduced if and
   * only if {@link ReduceExpressionsRule.Config#treatDynamicCallsAsConstant()}
   * is true. */
  @Test void testReduceDynamic() {
    checkDynamicFunctions(true).check();
  }

  /** As {@link #testReduceDynamic()}. */
  @Test void testNoReduceDynamic() {
    checkDynamicFunctions(false).checkUnchanged();
  }

  RelOptFixture checkDynamicFunctions(boolean treatDynamicCallsAsConstant) {
    // Create a customized executor with given context operator that reduces
    // "USER" to "happyCalciteUser"
    final RexExecutorImpl executor =
        new RexExecutorImpl(
            DataContexts.of(name ->
                name.equals(DataContext.Variable.USER.camelName)
                    ? "happyCalciteUser"
                    : fail("unknown: " + name)));

    RelOptPlanner planner = new MockRelOptPlanner(Contexts.empty());
    planner.setExecutor(executor);

    final ReduceExpressionsRule<?> rule =
        CoreRules.PROJECT_REDUCE_EXPRESSIONS.config
            .withOperandFor(LogicalProject.class)
            .withTreatDynamicCallsAsConstant(treatDynamicCallsAsConstant)
            .as(ProjectReduceExpressionsRule.Config.class)
            .toRule();

    final String sql = "select USER from emp";
    return sql(sql)
        .withFactory(t -> t.withPlannerFactory(context -> planner))
        .withRule(rule);
  }

  @Test void testReduceConstantsIsNull() {
    final String sql = "select empno from emp where empno=10 and empno is null";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceConstantsIsNotNull() {
    final String sql = "select empno from emp\n"
        + "where empno=10 and empno is not null";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  @Test void testReduceConstantsNegated() {
    final String sql = "select empno from emp\n"
        + "where empno=10 and not(empno=10)";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceConstantsNegatedInverted() {
    final String sql = "select empno from emp where empno>10 and empno<=10";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2638">[CALCITE-2638]
   * Constant reducer must not duplicate calls to non-deterministic
   * functions</a>. */
  @Test void testReduceConstantsNonDeterministicFunction() {
    final SqlOperator nonDeterministicOp =
        SqlBasicFunction.create("NDC", ReturnTypes.INTEGER,
            OperandTypes.VARIADIC).withDeterministic(false);

    // Build a tree equivalent to the SQL
    //  SELECT sal, n
    //  FROM (SELECT sal, NDC() AS n FROM emp)
    //  WHERE n > 10
    final Function<RelBuilder, RelNode> relFn = b ->
        b.scan("EMP")
            .project(b.field("SAL"),
                b.alias(b.call(nonDeterministicOp), "N"))
            .filter(b.greaterThan(b.field("N"), b.literal(10)))
            .build();

    relFn(relFn)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS, CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  /** Checks that constant reducer duplicates calls to dynamic functions, if
   * appropriate. CURRENT_TIMESTAMP is a dynamic function. */
  @Test void testReduceConstantsDynamicFunction() {
    final String sql = "select sal, t\n"
        + "from (select sal, current_timestamp t from emp)\n"
        + "where t > TIMESTAMP '2018-01-01 00:00:00'";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  @Test void testCasePushIsAlwaysWorking() {
    final String sql = "select empno from emp"
        + " where case when sal > 1000 then empno else sal end = 1";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.CALC_REDUCE_EXPRESSIONS,
            CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceValuesNull() {
    // The NULL literal presents pitfalls for value-reduction. Only
    // an INSERT statement contains un-CASTed NULL values.
    final String sql = "insert into EMPNULLABLES(EMPNO, ENAME, JOB) (select 0, 'null', NULL)";
    sql(sql).withRule(CoreRules.PROJECT_VALUES_MERGE).check();
  }

  @Test void testReduceValuesToEmpty() {
    // Plan should be same as for
    // select * from (values (11, 1, 10), (23, 3, 20)) as t(x, b, a)");
    final String sql = "select a + b as x, b, a from (values (10, 1), (30, 7)) as t(a, b)\n"
        + "where a - b < 0";
    sql(sql)
        .withRule(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_FILTER_VALUES_MERGE)
        .check();
  }

  @Test void testReduceConstantsWindow() {
    final String sql = "select col1, col2, col3\n"
        + "from (\n"
        + "  select empno,\n"
        + "    sum(100) over (partition by deptno, sal order by sal) as col1,\n"
        + "    sum(100) over (partition by sal order by deptno) as col2,\n"
        + "    sum(sal) over (partition by deptno order by sal) as col3\n"
        + "  from emp where sal = 5000)";

    sql(sql)
        .withRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW,
            CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_WINDOW_TRANSPOSE,
            CoreRules.WINDOW_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceConstantsWithMultipleOrderByWindow() {
    final String sql = "select col1, col2\n"
        + "from (\n"
        + "  select empno,\n"
        + "    sum(100) over (order by deptno, empno range between current row and unbounded following) as col1,\n"
        + "    sum(100) over (partition by sal, deptno order by deptno, empno range between unbounded preceding and unbounded following) as col2\n"
        + "  from emp where sal = 5000)";

    sql(sql)
        .withRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW,
            CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_WINDOW_TRANSPOSE,
            CoreRules.WINDOW_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testProjectOverWithAvg() {
    final String sql = "select avg(sal) over (order by empno rows 3 preceding) from emp";

    sql(sql)
        .withRule(CoreRules.PROJECT_OVER_SUM_TO_SUM0_RULE,
            CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW,
            CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_WINDOW_TRANSPOSE,
            CoreRules.WINDOW_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testEmptyFilterProjectUnion() {
    // Plan should be same as for
    // select * from (values (30, 3)) as t(x, y)");
    final String sql = "select * from (\n"
        + "select * from (values (10, 1), (30, 3)) as t (x, y)\n"
        + "union all\n"
        + "select * from (values (20, 2))\n"
        + ")\n"
        + "where x + y > 30";
    sql(sql)
        .withRule(CoreRules.FILTER_SET_OP_TRANSPOSE,
            CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_FILTER_VALUES_MERGE,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.UNION_INSTANCE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6955">[CALCITE-6955]
   * PruneEmptyRules does not handle the all attribute of SetOp correctly</a>. */
  @Test void testEmptyFilterProjectUnion2() {
    final String sql = "select * from (\n"
        + "select * from (values (10, 1), (30, 3), (30, 3)) as t (x, y)\n"
        + "union\n"
        + "select * from (values (20, 2))\n"
        + ")\n"
        + "where x + y > 30";
    sql(sql)
        .withRule(CoreRules.FILTER_SET_OP_TRANSPOSE,
            CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_FILTER_VALUES_MERGE,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.UNION_INSTANCE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1488">[CALCITE-1488]
   * ValuesReduceRule should ignore empty Values</a>. */
  @Test void testEmptyProject() {
    final String sql = "select z + x from (\n"
        + "  select x + y as z, x from (\n"
        + "    select * from (values (10, 1), (30, 3)) as t (x, y)\n"
        + "    where x + y > 50))";
    sql(sql)
        .withRule(CoreRules.PROJECT_FILTER_VALUES_MERGE,
            CoreRules.FILTER_VALUES_MERGE,
            CoreRules.PROJECT_VALUES_MERGE)
        .check();
  }

  /** Same query as {@link #testEmptyProject()}, and {@link PruneEmptyRules}
   * is able to do the job that {@link ValuesReduceRule} cannot do. */
  @Test void testEmptyProject2() {
    final String sql = "select z + x from (\n"
        + "  select x + y as z, x from (\n"
        + "    select * from (values (10, 1), (30, 3)) as t (x, y)\n"
        + "    where x + y > 50))";
    sql(sql)
        .withRule(CoreRules.FILTER_VALUES_MERGE,
            PruneEmptyRules.PROJECT_INSTANCE)
        .check();
  }

  @Test void testEmptyIntersect() {
    final String sql = "select * from (values (30, 3))"
        + "intersect\n"
        + "select *\nfrom (values (10, 1), (30, 3)) as t (x, y) where x > 50\n"
        + "intersect\n"
        + "select * from (values (30, 3))";
    sql(sql)
        .withRule(CoreRules.PROJECT_FILTER_VALUES_MERGE,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.INTERSECT_INSTANCE)
        .check();
  }

  @Test void testEmptyMinus() {
    // First input is empty; therefore whole expression is empty
    final String sql = "select * from (values (30, 3)) as t (x, y)\n"
        + "where x > 30\n"
        + "except\n"
        + "select * from (values (20, 2))\n"
        + "except\n"
        + "select * from (values (40, 4))";
    sql(sql)
        .withRule(CoreRules.PROJECT_FILTER_VALUES_MERGE,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.MINUS_INSTANCE)
        .check();
  }

  @Test void testEmptyMinus2() {
    // Second and fourth inputs are empty; they are removed
    final String sql = "select * from (values (30, 3)) as t (x, y)\n"
        + "except\n"
        + "select * from (values (20, 2)) as t (x, y) where x > 30\n"
        + "except\n"
        + "select * from (values (40, 4))\n"
        + "except\n"
        + "select * from (values (50, 5)) as t (x, y) where x > 50";
    sql(sql)
        .withRule(CoreRules.PROJECT_FILTER_VALUES_MERGE,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.MINUS_INSTANCE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6955">[CALCITE-6955]
   * PruneEmptyRules does not handle the all attribute of SetOp correctly</a>. */
  @Test void testEmptyMinus3() {
    // First input is empty; therefore whole expression is empty
    final String sql = "select * from (values (30, 3), (30, 3)) as t (x, y)\n"
        + "except\n"
        + "select * from (values (20, 2)) as t (x, y) where x > 30";
    sql(sql)
        .withRule(CoreRules.PROJECT_FILTER_VALUES_MERGE,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.MINUS_INSTANCE)
        .check();
  }

  @Test void testEmptyTable() {
    // table is transformed to empty values and extra project will be removed.
    final String sql = "select * from EMPTY_PRODUCTS\n";
    sql(sql)
        .withRule(
            PruneEmptyRules.EMPTY_TABLE_INSTANCE,
            PruneEmptyRules.PROJECT_INSTANCE)
        .check();
  }

  @Test void testJoinWithConstantRowOnRight() {
    final String sql = "select e.empno, e.ename, c.*"
        + " from emp e cross join (select 5, 5) c";

    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_RIGHT_INSTANCE)
        .check();
  }

  @Test void testJoinWithConstantRowOnLeft() {
    final String sql = "select e.empno, e.ename, c.*"
        + " from (select 5, 5) c cross join emp e";

    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_LEFT_INSTANCE)
        .check();
  }

  @Test void testInnerJoinWithConstantRowOnLeft() {
    final String sql = "select e.empno, e.ename, c.*"
        + " from (select 5 as nm) c inner join emp e on e.empno = c.nm";

    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_LEFT_INSTANCE)
        .check();
  }

  @Test void testInnerJoinWithConstantRowOnRight() {
    final String sql = "select e.empno, e.ename, c.*"
        + " from emp e inner join (select 5 as nm) c  on e.empno = c.nm";

    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_RIGHT_INSTANCE)
        .check();
  }

  @Test void testRightJoinWithConstantRowOnLeft() {
    final String sql = "select e.empno, e.ename, c.*"
        + " from (select 5 as nm) c right join emp e on e.empno = c.nm";

    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_LEFT_INSTANCE)
        .check();
  }

  @Test void testLeftJoinWithConstantRowOnRight() {
    final String sql = "select e.empno, e.ename, c.*"
        + " from emp e left join (select 5 as nm) c  on e.empno = c.nm";

    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_RIGHT_INSTANCE)
        .check();
  }

  @Test void testInnerJoinWithTimeStampSingleRowOnRight() {
    final String sql = "select e.empno, e.ename, c.t"
        + " from emp e inner join (select 7934 as ono, current_timestamp as t) c on e.empno=c.ono";
    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_RIGHT_PROJECT_INSTANCE)
        .check();
  }

  @Test void testLeftJoinWithTimeStampSingleRowOnRight() {
    final String sql = "select e.empno, e.ename, c.t "
        + "from emp e left join (select 7934 as ono, current_timestamp as t) c "
        + "  on e.empno = c.ono";
    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_RIGHT_PROJECT_INSTANCE)
        .check();
  }

  @Test void testCrossJoinWithTimeStampSingleRowOnRight() {
    final String sql = "select e.empno, e.ename, c.*"
        + " from emp e cross join (select 5, current_timestamp) c";
    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_RIGHT_PROJECT_INSTANCE)
        .check();
  }
  @Test void testInnerJoinWithTimeStampSingleRowOnLeft() {
    final String sql = "select e.empno, e.ename, c.t"
        + " from (select 7934 as ono, current_timestamp as t) c inner join emp e on e.empno=c.ono";
    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_LEFT_PROJECT_INSTANCE)
        .check();
  }

  @Test void testRightJoinWithTimeStampSingleRowOnLeft() {
    final String sql = "select e.empno, e.ename, c.t "
        + "from (select 7934 as ono, current_timestamp as t) c right join emp e "
        + "  on e.empno = c.ono";
    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_LEFT_PROJECT_INSTANCE)
        .check();
  }

  @Test void testCrossJoinWithTimeStampSingleRowOnLeft() {
    final String sql = "select e.empno, e.ename, c.*"
        + " from (select 5, current_timestamp) c cross join emp e ";
    sql(sql)
        .withRule(SingleValuesOptimizationRules.JOIN_LEFT_PROJECT_INSTANCE)
        .check();
  }

  @Test void testEmptyTableTransformsComplexQueryToSingleTableScan() {
    final String sql = "select products.PRODUCTID, products.NAME from products left join\n"
        + "(select * from products as e\n"
        + " inner join EMPTY_PRODUCTS as d on e.PRODUCTID = d.PRODUCTID\n"
        + " where e.SUPPLIERID > 10) dt\n"
        + " on products.PRODUCTID = dt.PRODUCTID";
    Collection<RelOptRule> rules =
        Arrays.asList(PruneEmptyRules.EMPTY_TABLE_INSTANCE,
            PruneEmptyRules.JOIN_RIGHT_INSTANCE,
            PruneEmptyRules.FILTER_INSTANCE,
            PruneEmptyRules.PROJECT_INSTANCE,
            CoreRules.PROJECT_MERGE);
    sql(sql)
        .withProgram(HepProgram.builder().addRuleCollection(rules).build())
        .check();
  }

  @Test void testLeftEmptyInnerJoin() {
    // Plan should be empty
    final String sql = "select * from (\n"
        + "select * from emp where false) as e\n"
        + "join dept as d on e.deptno = d.deptno";
    checkEmptyJoin(sql(sql));
  }

  @Test void testLeftEmptyLeftJoin() {
    // Plan should be empty
    final String sql = "select * from (\n"
        + "  select * from emp where false) e\n"
        + "left join dept d on e.deptno = d.deptno";
    checkEmptyJoin(sql(sql));
  }

  @Test void testLeftEmptyRightJoin() {
    // Plan should be equivalent to "select * from emp right join dept".
    // Cannot optimize away the join because of RIGHT.
    final String sql = "select * from (\n"
        + "  select * from emp where false) e\n"
        + "right join dept d on e.deptno = d.deptno";
    checkEmptyJoin(sql(sql));
  }

  @Test void testLeftEmptyFullJoin() {
    // Plan should be equivalent to "select * from emp full join dept".
    // Cannot optimize away the join because of FULL.
    final String sql = "select * from (\n"
        + "  select * from emp where false) e\n"
        + "full join dept d on e.deptno = d.deptno";
    checkEmptyJoin(sql(sql));
  }

  @Test void testLeftEmptySemiJoin() {
    checkLeftEmptySemiOrAntiJoin(JoinRelType.SEMI);
  }

  @Test void testLeftEmptyAntiJoin() {
    checkLeftEmptySemiOrAntiJoin(JoinRelType.ANTI);
  }

  private void checkLeftEmptySemiOrAntiJoin(JoinRelType type) {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP").empty()
        .scan("DEPT")
        .join(type, b
            .equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO")))
        .project(b.field("EMPNO"))
        .build();
    checkEmptyJoin(relFn(relFn));
  }

  @Test void testRightEmptyInnerJoin() {
    // Plan should be empty
    final String sql = "select * from emp e\n"
        + "join (select * from dept where false) as d\n"
        + "on e.deptno = d.deptno";
    checkEmptyJoin(sql(sql));
  }

  @Test void testRightEmptyLeftJoin() {
    // Plan should be equivalent to "select * from emp left join dept".
    // Cannot optimize away the join because of LEFT.
    final String sql = "select * from emp e\n"
        + "left join (select * from dept where false) as d\n"
        + "on e.deptno = d.deptno";
    checkEmptyJoin(sql(sql));
  }

  @Test void testRightEmptyRightJoin() {
    // Plan should be empty
    final String sql = "select * from emp e\n"
        + "right join (select * from dept where false) as d\n"
        + "on e.deptno = d.deptno";
    checkEmptyJoin(sql(sql));
  }

  @Test void testRightEmptyFullJoin() {
    // Plan should be equivalent to "select * from emp full join dept".
    // Cannot optimize away the join because of FULL.
    final String sql = "select * from emp e\n"
        + "full join (select * from dept where false) as d\n"
        + "on e.deptno = d.deptno";
    checkEmptyJoin(sql(sql));
  }

  @Test void testRightEmptySemiJoin() {
    checkRightEmptyAntiJoin(JoinRelType.SEMI);
  }

  @Test void testRightEmptyAntiJoin() {
    checkRightEmptyAntiJoin(JoinRelType.ANTI);
  }

  private void checkRightEmptyAntiJoin(JoinRelType type) {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .scan("DEPT").empty()
        .join(type, b
            .equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO")))
        .project(b.field("EMPNO"))
        .build();
    checkEmptyJoin(relFn(relFn));
  }

  @Test void testRightEmptyAntiJoinNonEqui() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .scan("DEPT").empty()
        .antiJoin(b
            .equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO")),
            b
                .equals(
                    b.field(2, 0, "SAL"),
                    b.literal(2000)))
        .project(b.field("EMPNO"))
        .build();
    checkEmptyJoin(relFn(relFn));
  }

  @Test void testCorrelateWithoutCorrelationToEmpty() {
    String sql = "select * from emp e where exists (select 1 from dept where empno=null)";
    List<RelOptRule> rules =
        Arrays.asList(CoreRules.FILTER_REDUCE_EXPRESSIONS,
        PruneEmptyRules.PROJECT_INSTANCE,
        PruneEmptyRules.AGGREGATE_INSTANCE,
        PruneEmptyRules.JOIN_RIGHT_INSTANCE,
        PruneEmptyRules.CORRELATE_RIGHT_INSTANCE);
    sql(sql)
        .withExpand(false)
        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withProgram(HepProgram.builder().addRuleCollection(rules).build())
        .check();
  }

  @Test void testCorrelateWithoutCorrelationToEmptyInVolcano() {
    String sql = "select * from emp e where exists (select 1 from dept where empno=null)";
    List<RelOptRule> rules =
        Arrays.asList(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.AGGREGATE_INSTANCE,
            PruneEmptyRules.JOIN_RIGHT_INSTANCE,
            PruneEmptyRules.CORRELATE_RIGHT_INSTANCE);
    sql(sql)
        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withVolcanoPlanner(false, p -> {
          rules.forEach(p::addRule);
          p.addRule(EnumerableRules.ENUMERABLE_VALUES_RULE);
        })
        .withExpand(false)
        .check();
  }

  @Test void testInnerCorrelateWithLeftEmpty() {
    relFn(correlationWithEmpty(JoinRelType.INNER, true, false))
        .withRule(PruneEmptyRules.CORRELATE_LEFT_INSTANCE)
        .check();
  }

  @Test void testLeftCorrelateWithLeftEmpty() {
    relFn(correlationWithEmpty(JoinRelType.LEFT, true, false))
        .withRule(PruneEmptyRules.CORRELATE_LEFT_INSTANCE)
        .check();
  }

  @Test void testSemiCorrelateWithLeftEmpty() {
    relFn(correlationWithEmpty(JoinRelType.SEMI, true, false))
        .withRule(PruneEmptyRules.CORRELATE_LEFT_INSTANCE)
        .check();
  }

  @Test void testAntiCorrelateWithLeftEmpty() {
    relFn(correlationWithEmpty(JoinRelType.ANTI, true, false))
        .withRule(PruneEmptyRules.CORRELATE_LEFT_INSTANCE)
        .check();
  }

  @Test void testInnerCorrelateWithRightEmpty() {
    relFn(correlationWithEmpty(JoinRelType.INNER, false, true))
        .withRule(PruneEmptyRules.CORRELATE_RIGHT_INSTANCE)
        .check();
  }

  @Test void testLeftCorrelateWithRightEmpty() {
    relFn(correlationWithEmpty(JoinRelType.LEFT, false, true))
        .withRule(PruneEmptyRules.CORRELATE_RIGHT_INSTANCE)
        .check();
  }

  @Test void testSemiCorrelateWithRightEmpty() {
    relFn(correlationWithEmpty(JoinRelType.SEMI, false, true))
        .withRule(PruneEmptyRules.CORRELATE_RIGHT_INSTANCE)
        .check();
  }

  @Test void testAntiCorrelateWithRightEmpty() {
    relFn(correlationWithEmpty(JoinRelType.ANTI, false, true))
        .withRule(PruneEmptyRules.CORRELATE_RIGHT_INSTANCE)
        .check();
  }

  @Test void testInnerCorrelateWithNoneEmpty() {
    relFn(correlationWithEmpty(JoinRelType.INNER, false, false))
        .withRule(PruneEmptyRules.CORRELATE_RIGHT_INSTANCE)
        .withRule(PruneEmptyRules.CORRELATE_LEFT_INSTANCE)
        .checkUnchanged();
  }

  @Test void testLeftCorrelateWithBothEmpty1() {
    relFn(correlationWithEmpty(JoinRelType.LEFT, true, true))
        .withRule(PruneEmptyRules.CORRELATE_RIGHT_INSTANCE)
        .check();
  }

  @Test void testLeftCorrelateWithBothEmpty2() {
    relFn(correlationWithEmpty(JoinRelType.LEFT, true, true))
        .withRule(PruneEmptyRules.CORRELATE_LEFT_INSTANCE)
        .check();
  }

  @Test void testLeftCorrelateWithBothEmpty3() {
    List<RelOptRule> rules =
        Arrays.asList(PruneEmptyRules.CORRELATE_RIGHT_INSTANCE,
        PruneEmptyRules.CORRELATE_LEFT_INSTANCE);
    relFn(correlationWithEmpty(JoinRelType.LEFT, true, true))
        .withProgram(HepProgram.builder().addRuleCollection(rules).build())
        .check();
  }

  private static Function<RelBuilder, RelNode> correlationWithEmpty(JoinRelType joinType,
      boolean emptyLeft, boolean emptyRight) {
    final Holder<RexCorrelVariable> v = Holder.empty();
    return b -> {
      List<RexNode> requiredFields = new ArrayList<>();
      b.scan("EMP").variable(v::set);
      if (emptyLeft) {
        b.empty();
      }
      requiredFields.add(b.field("EMP", "DEPTNO"));
      b.scan("DEPT").filter(b.equals(b.field(v.get(), "DEPTNO"), b.field("DEPTNO")));
      if (emptyRight) {
        b.empty();
      }
      return b.correlate(joinType, v.get().id, requiredFields).build();
    };
  }

  private void checkEmptyJoin(RelOptFixture f) {
    f.withRule(
        CoreRules.FILTER_REDUCE_EXPRESSIONS,
        PruneEmptyRules.PROJECT_INSTANCE,
        PruneEmptyRules.JOIN_LEFT_INSTANCE,
        PruneEmptyRules.JOIN_RIGHT_INSTANCE).check();
  }

  @Test void testEmptySort() {
    final String sql = "select * from emp where false order by deptno";
    sql(sql)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.SORT_INSTANCE)
        .check();
  }

  @Test void testEmptySort2() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("DEPT").empty()
        .sort(
            b.field("DNAME"),
            b.field("DEPTNO"))
        .build();
    relFn(relFn).withRule(PruneEmptyRules.SORT_INSTANCE).check();
  }

  @Test void testEmptySortLimitZero() {
    final String sql = "select * from emp order by deptno limit 0";
    sql(sql).withRule(PruneEmptyRules.SORT_FETCH_ZERO_INSTANCE).check();
  }

  @Test void testEmptyAggregate() {
    final String sql = "select sum(empno) from emp where false group by deptno";
    sql(sql)
        .withPreRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            PruneEmptyRules.PROJECT_INSTANCE)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            PruneEmptyRules.PROJECT_INSTANCE,
            PruneEmptyRules.AGGREGATE_INSTANCE,
            PruneEmptyRules.PROJECT_INSTANCE)
        .check();
  }

  @Test void testEmptyAggregateEmptyKey() {
    final String sql = "select sum(empno) from emp where false";
    sql(sql)
        .withPreRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            PruneEmptyRules.PROJECT_INSTANCE)
        .withRule(PruneEmptyRules.AGGREGATE_INSTANCE)
        .checkUnchanged();
  }

  @Test void testEmptyAggregateEmptyKeyWithAggregateValuesRule() {
    final String sql = "select count(*), sum(empno) from emp where false";
    sql(sql)
        .withPreRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            PruneEmptyRules.PROJECT_INSTANCE)
        .withRule(CoreRules.AGGREGATE_VALUES)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5117">[CALCITE-5117]
   * Optimize the EXISTS sub-query by Metadata RowCount</a>. */
  @Test void testExistsWithAtLeastOneRowSubQuery() {
    final String sql = "select * from dept as d\n"
        + "where EXISTS (\n"
        + "  select count(*) from emp e where d.deptno = e.deptno)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testExistsWithNoRowSubQuery() {
    final String sql = "select * from dept as d\n"
        + "where NOT EXISTS (\n"
        + "  select count(*) from emp e having false)";
    sql(sql).withSubQueryRules().check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6650">[CALCITE-6650]
   * Optimize the IN sub-query and SOME sub-query
   * by Metadata RowCount</a>. */
  @Test void testInWithNoRowSubQuery() {
    final String sql = "select * from dept as d\n"
        + "where deptno in (\n"
        + "  select deptno from emp e where false)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testNotInWithNoRowSubQuery() {
    final String sql = "select * from dept as d\n"
        + "where deptno not in (\n"
        + "  select deptno from emp e where false)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testSomeWithGreaterThanNoRowSubQuery() {
    final String sql = "select * from dept as d\n"
        + "where deptno > some(\n"
        + "  select deptno from emp e where false)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testSomeWithLessThanOrEqualNoRowSubQuery() {
    final String sql = "select * from dept as d\n"
        + "where deptno <= some(\n"
        + "  select deptno from emp e where false)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testUniqueWithNoRowSubQuery() {
    final String sql = "select * from dept as d\n"
        + "where unique(\n"
        + "  select deptno from emp e where false)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testNotUniqueWithNoRowSubQuery() {
    final String sql = "select * from dept as d\n"
        + "where not unique(\n"
        + "  select deptno from emp e where false)";
    sql(sql).withSubQueryRules().check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4848">[CALCITE-4848]
   * Adding a HAVING condition to a query with a dynamic parameter makes the result always empty
   </a>. */
  @Test void testAggregateWithDynamicParam() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.FILTER_REDUCE_EXPRESSIONS);
    final String sql = "SELECT sal, COUNT(1) AS count_val\n"
        + "FROM emp t WHERE sal = ?\n"
        + "GROUP BY sal HAVING sal < 1000";
    sql(sql).withPlanner(hepPlanner)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6647">[CALCITE-6647]
   * SortUnionTransposeRule should not push SORT past a UNION when SORT's fetch is DynamicParam
   </a>. */
  @Test void testSortWithDynamicParam() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(SortProjectTransposeRule.class);
    builder.addRuleClass(SortUnionTransposeRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.SORT_PROJECT_TRANSPOSE);
    hepPlanner.addRule(CoreRules.SORT_UNION_TRANSPOSE);
    final String sql = "SELECT x.sal\n"
        + "FROM (SELECT emp1.sal\n"
        + "      FROM (SELECT sal\n"
        + "            from emp\n"
        + "            LIMIT ?) AS emp1\n"
        + "      UNION ALL\n"
        + "      SELECT emp2.sal\n"
        + "      FROM (SELECT sal\n"
        + "            from emp\n"
        + "            LIMIT ?) AS emp2) AS x\n"
        + "LIMIT ?";
    sql(sql).withPlanner(hepPlanner).check();
  }

  @Test void testReduceCasts() {
    // Disable simplify in RelBuilder so that there are casts in 'before';
    // The resulting plan should have no cast expressions
    final String sql = "select cast(d.name as varchar(128)), cast(e.empno as integer)\n"
        + "from dept as d inner join emp as e\n"
        + "on cast(d.deptno as integer) = cast(e.deptno as integer)\n"
        + "where cast(e.job as varchar(1)) = 'Manager'";
    sql(sql)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .withRelBuilderSimplify(false)
        .check();
  }

  /** Tests that a cast from a TIME to a TIMESTAMP is not reduced. It is not
   * constant because the result depends upon the current date. */
  @Test void testReduceCastTimeUnchanged() {
    sql("select cast(time '12:34:56' as timestamp) from emp as e")
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  @Test void testReduceCastAndConsts() {
    // Make sure constant expressions inside the cast can be reduced
    // in addition to the casts.
    final String sql = "select * from emp\n"
        + "where cast((empno + (10/2)) as int) = 13";
    sql(sql).withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS).check();
  }

  @Test void testReduceCaseNullabilityChange() {
    final String sql = "select case when empno = 1 then 1\n"
        + "when 1 IS NOT NULL then 2\n"
        + "else null end as qx "
        + "from emp";
    sql(sql)
        .withRelBuilderSimplify(false)
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testReduceCastsNullable() {
    HepProgram program = new HepProgramBuilder()

        // Simulate the way INSERT will insert casts to the target types
        .addRuleInstance(
            CoerceInputsRule.Config.DEFAULT
                .withCoerceNames(false)
                .withConsumerRelClass(LogicalTableModify.class)
                .toRule())

            // Convert projects to calcs, merge two calcs, and then
            // reduce redundant casts in merged calc.
        .addRuleInstance(CoreRules.PROJECT_TO_CALC)
        .addRuleInstance(CoreRules.CALC_MERGE)
        .addRuleInstance(CoreRules.CALC_REDUCE_EXPRESSIONS)
        .build();
    final String sql = "insert into sales.dept(deptno, name)\n"
        + "select empno, cast(job as varchar(128)) from sales.empnullables";
    sql(sql).withProgram(program).check();
  }

  @Test void testReduceCaseWhenWithCast() {
    final Function<RelBuilder, RelNode> relFn = b -> {
      final RexBuilder rexBuilder = b.getRexBuilder();
      final RelDataType type = rexBuilder.getTypeFactory().createSqlType(SqlTypeName.BIGINT);
      final RelDataType nullableType = rexBuilder.getTypeFactory()
          .createTypeWithNullability(type, true);

      RelNode left = b
          .values(new String[]{"x", "y"}, 1, 2, 2, 1).build();
      RexNode ref = rexBuilder.makeInputRef(left, 0);
      RexLiteral literal1 = rexBuilder.makeLiteral(1, type);
      RexLiteral literal2 = rexBuilder.makeLiteral(2, type);
      RexLiteral literal3 = rexBuilder.makeLiteral(3, type);
      // the MOD (%) operation needs to be unsafe to reproduce the scenario
      RexNode param0 = rexBuilder.makeDynamicParam(nullableType, 0);
      RexNode param1 = rexBuilder.makeDynamicParam(nullableType, 1);

      // CASE WHEN x % param0 = 1 THEN x < 2
      //      WHEN x % param1 = 2 THEN x < 1
      //      ELSE x < 3
      final RexNode caseRexNode =
          rexBuilder.makeCall(
              SqlStdOperatorTable.CASE,
              rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
                  rexBuilder.makeCall(SqlStdOperatorTable.MOD, ref, param0),
                  literal1),
              rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, literal2),
              rexBuilder.makeCall(SqlStdOperatorTable.EQUALS,
                  rexBuilder.makeCall(SqlStdOperatorTable.MOD, ref, param1),
                  literal2),
              rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, literal1),
              rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN, ref, literal3));

      final RexNode castNode =
          rexBuilder.makeCast(
              rexBuilder.getTypeFactory()
                  .createTypeWithNullability(caseRexNode.getType(), true),
              caseRexNode);
      return b
          .push(left)
          .project(castNode)
          .build();
    };

    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ReduceExpressionsRule.class);

    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS);

    relFn(relFn).withPlanner(hepPlanner).checkUnchanged();
  }

  private void basePushAggThroughUnion() {
    sql("${sql}")
        .withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE,
            CoreRules.PROJECT_MERGE,
            CoreRules.AGGREGATE_UNION_TRANSPOSE)
        .check();
  }

  @Test void testPushSumConstantThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushSumNullConstantThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushSumNullableThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushSumNullableNOGBYThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushCountStarThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushCountNullableThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushMaxNullableThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushMinThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushAvgThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushSumCountStarThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushSumConstantGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushSumNullConstantGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushSumNullableGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushCountStarGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushCountNullableGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushMaxNullableGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushMinGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushAvgGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushSumCountStarGroupingSetsThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushCountFilterThroughUnion() {
    basePushAggThroughUnion();
  }

  @Test void testPushBoolAndBoolOrThroughUnion() {
    sql("${sql}")
        .withFactory(f ->
            f.withOperatorTable(opTab ->
                SqlValidatorTest.operatorTableFor(SqlLibrary.POSTGRESQL)))
        .withRule(CoreRules.PROJECT_SET_OP_TRANSPOSE,
            CoreRules.PROJECT_MERGE,
            CoreRules.AGGREGATE_UNION_TRANSPOSE)
        .check();
  }

  @Test void testPullFilterThroughAggregate() {
    final String sql = "select ename, sal, deptno from ("
        + "  select ename, sal, deptno"
        + "  from emp"
        + "  where sal > 5000)"
        + "group by ename, sal, deptno";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.AGGREGATE_FILTER_TRANSPOSE)
        .check();
  }

  @Test void testPullFilterThroughAggregateGroupingSets() {
    final String sql = "select ename, sal, deptno from ("
        + "  select ename, sal, deptno"
        + "  from emp"
        + "  where sal > 5000)"
        + "group by rollup(ename, sal, deptno)";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.AGGREGATE_FILTER_TRANSPOSE)
        .check();
  }

  private void basePullConstantTroughAggregate() {
    sql("${sql}")
        .withRule(CoreRules.PROJECT_MERGE,
            CoreRules.AGGREGATE_PROJECT_PULL_UP_CONSTANTS,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testPullConstantThroughConstLast() {
    basePullConstantTroughAggregate();
  }

  @Test void testPullConstantThroughAggregateSimpleNonNullable() {
    basePullConstantTroughAggregate();
  }

  @Test void testPullConstantThroughAggregatePermuted() {
    basePullConstantTroughAggregate();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6586">[CALCITE-6586]
   * Some Rules not firing due to RelMdPredicates returning null in VolcanoPlanner</a>. */
  @Test void testPullConstantThroughAggregatePermutedInVolcano() {
    sql("${sql}")
        .withVolcanoPlanner(false, p -> {
          p.addRule(CoreRules.AGGREGATE_PROJECT_PULL_UP_CONSTANTS);
          p.addRule(CoreRules.PROJECT_MERGE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_AGGREGATE_RULE);
        })
        .check();
  }

  @Test void testPullConstantThroughAggregatePermutedConstFirst() {
    basePullConstantTroughAggregate();
  }

  @Test void testPullConstantThroughAggregatePermutedConstGroupBy() {
    basePullConstantTroughAggregate();
  }

  @Test void testPullConstantThroughAggregateConstGroupBy() {
    basePullConstantTroughAggregate();
  }

  @Test void testPullConstantThroughAggregateAllConst() {
    basePullConstantTroughAggregate();
  }

  @Test void testPullConstantThroughAggregateAllLiterals() {
    basePullConstantTroughAggregate();
  }

  @Test void testPullConstantThroughUnion() {
    final String sql = "select 2, deptno, job from emp as e1\n"
        + "union all\n"
        + "select 2, deptno, job from emp as e2";
    sql(sql)
        .withTrim(true)
        .withRule(CoreRules.UNION_PULL_UP_CONSTANTS,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6586">[CALCITE-6586]
   * Some Rules not firing due to RelMdPredicates returning null in VolcanoPlanner</a>. */
  @Test void testPullConstantThroughUnionInVolcano() {
    final String sql = "select 2, deptno, job from emp as e1\n"
        + "union all\n"
        + "select 2, deptno, job from emp as e2";
    sql(sql)
        .withTrim(true)
        .withVolcanoPlanner(false, p -> {
          p.addRule(CoreRules.UNION_PULL_UP_CONSTANTS);
          p.addRule(CoreRules.PROJECT_MERGE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_UNION_RULE);
        })
        .check();
  }

  @Test void testPullConstantThroughUnion2() {
    // Negative test: constants should not be pulled up
    final String sql = "select 2, deptno, job from emp as e1\n"
        + "union all\n"
        + "select 1, deptno, job from emp as e2";
    sql(sql)
        .withRule(CoreRules.UNION_PULL_UP_CONSTANTS,
            CoreRules.PROJECT_MERGE)
        .checkUnchanged();
  }

  @Test void testPullConstantThroughUnion3() {
    // We should leave at least a single column in each Union input
    final String sql = "select 2, 3 from emp as e1\n"
        + "union all\n"
        + "select 2, 3 from emp as e2";
    sql(sql)
        .withTrim(true)
        .withRule(CoreRules.UNION_PULL_UP_CONSTANTS,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testPullConstantThroughUnionSameTypeNullableField() {
    final String sql = "select deptno, ename from empnullables where deptno = 1\n"
        + "union all\n"
        + "select deptno, ename from empnullables where deptno = 1";
    sql(sql)
        .withTrim(true)
        .withRule(CoreRules.UNION_PULL_UP_CONSTANTS,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testAggregateProjectMerge() {
    final String sql = "select x, sum(z), y from (\n"
        + "  select deptno as x, empno as y, sal as z, sal * 2 as zz\n"
        + "  from emp)\n"
        + "group by x, y";
    sql(sql).withRule(CoreRules.AGGREGATE_PROJECT_MERGE).check();
  }

  @Test void testAggregateGroupingSetsProjectMerge() {
    final String sql = "select x, sum(z), y from (\n"
        + "  select deptno as x, empno as y, sal as z, sal * 2 as zz\n"
        + "  from emp)\n"
        + "group by rollup(x, y)";
    sql(sql).withRule(CoreRules.AGGREGATE_PROJECT_MERGE).check();
  }

  @Test void testAggregateExtractProjectRule() {
    final String sql = "select sum(sal)\n"
        + "from emp";
    HepProgram pre = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_PROJECT_MERGE)
        .build();
    sql(sql).withPre(pre).withRule(AggregateExtractProjectRule.SCAN).check();
  }

  @Test void testAggregateExtractProjectRuleWithGroupingSets() {
    final String sql = "select empno, deptno, sum(sal)\n"
        + "from emp\n"
        + "group by grouping sets ((empno, deptno),(deptno),(empno))";
    HepProgram pre = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_PROJECT_MERGE)
        .build();
    sql(sql).withPre(pre).withRule(AggregateExtractProjectRule.SCAN).check();
  }

  /** Test with column used in both grouping set and argument to aggregate
   * function. */
  @Test void testAggregateExtractProjectRuleWithGroupingSets2() {
    final String sql = "select empno, deptno, sum(empno)\n"
        + "from emp\n"
        + "group by grouping sets ((empno, deptno),(deptno),(empno))";
    HepProgram pre = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_PROJECT_MERGE)
        .build();
    sql(sql).withPre(pre).withRule(AggregateExtractProjectRule.SCAN).check();
  }

  @Test void testAggregateExtractProjectRuleWithFilter() {
    final String sql = "select sum(sal) filter (where empno = 40)\n"
        + "from emp";
    HepProgram pre = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_PROJECT_MERGE)
        .build();
    // AggregateProjectMergeRule does not merges Project with Filter.
    // Force match Aggregate on top of Project once explicitly in unit test.
    final AggregateExtractProjectRule rule =
        AggregateExtractProjectRule.SCAN.config
            .withOperandSupplier(b0 ->
                b0.operand(Aggregate.class).oneInput(b1 ->
                    b1.operand(Project.class)
                        .predicate(new Predicate<Project>() {
                          int matchCount = 0;

                          public boolean test(Project project) {
                            return matchCount++ == 0;
                          }
                        }).anyInputs()))
            .as(AggregateExtractProjectRule.Config.class)
            .toRule();
    sql(sql).withPre(pre).withRule(rule).checkUnchanged();
  }

  @Test void testAggregateCaseToFilter() {
    final String sql = "select\n"
        + " sum(sal) as sum_sal,\n"
        + " count(distinct case\n"
        + "       when job = 'CLERK'\n"
        + "       then deptno else null end) as count_distinct_clerk,\n"
        + " sum(case when deptno = 10 then sal end) as sum_sal_d10,\n"
        + " sum(case when deptno = 20 then sal else 0 end) as sum_sal_d20,\n"
        + " sum(case when deptno = 30 then 1 else 0 end) as count_d30,\n"
        + " count(case when deptno = 40 then 'x' end) as count_d40,\n"
        + " sum(case when deptno = 45 then 1 end) as count_d45,\n"
        + " sum(case when deptno = 50 then 1 else null end) as count_d50,\n"
        + " sum(case when deptno = 60 then null end) as sum_null_d60,\n"
        + " sum(case when deptno = 70 then null else 1 end) as sum_null_d70,\n"
        + " count(case when deptno = 20 then 1 end) as count_d20\n"
        + "from emp";
    sql(sql).withRule(CoreRules.AGGREGATE_CASE_TO_FILTER).check();
  }

  @Test void testAggregateCaseToFilterNoMatch() {
    final String sql = "select\n"
        + " sum(case when deptno = -1 then 1 else 0 end) as sum_no_match,\n"
        + " sum(case when deptno = -1 then 2 else 0 end) as sum_no_match2,\n"
        + " sum(case when deptno = -1 then 3 else -1 end) as sum_no_match3\n"
        + "from emp";
    sql(sql).withRule(CoreRules.AGGREGATE_CASE_TO_FILTER).checkUnchanged();
  }

  @Test void testPullAggregateThroughUnion() {
    final String sql = "select deptno, job from"
        + " (select deptno, job from emp as e1"
        + " group by deptno,job"
        + "  union all"
        + " select deptno, job from emp as e2"
        + " group by deptno,job)"
        + " group by deptno,job";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_UNION_AGGREGATE)
        .check();
  }

  @Test void testPullAggregateThroughUnion2() {
    final String sql = "select deptno, job from"
        + " (select deptno, job from emp as e1"
        + " group by deptno,job"
        + "  union all"
        + " select deptno, job from emp as e2"
        + " group by deptno,job)"
        + " group by deptno,job";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_UNION_AGGREGATE_SECOND,
            CoreRules.AGGREGATE_UNION_AGGREGATE_FIRST)
        .check();
  }

  /**
   * Once the bottom aggregate pulled through union, we need to add a Project
   * if the new input contains a different type from the union.
   */
  @Test void testPullAggregateThroughUnionAndAddProjects() {
    final String sql = "select job, deptno from"
        + " (select job, deptno from emp as e1"
        + " group by job, deptno"
        + "  union all"
        + " select job, deptno from emp as e2"
        + " group by job, deptno)"
        + " group by job, deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_UNION_AGGREGATE)
        .check();
  }

  /**
   * Make sure the union alias is preserved when the bottom aggregate is
   * pulled up through union.
   */
  @Test void testPullAggregateThroughUnionWithAlias() {
    final String sql = "select job, c from"
        + " (select job, deptno c from emp as e1"
        + " group by job, deptno"
        + "  union all"
        + " select job, deptno from emp as e2"
        + " group by job, deptno)"
        + " group by job, c";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_UNION_AGGREGATE)
        .check();
  }

  /**
   * Creates a {@link HepProgram} with common transitive rules.
   */
  private HepProgram getTransitiveProgram() {
    return new HepProgramBuilder()
        .addRuleInstance(CoreRules.FILTER_INTO_JOIN_DUMB)
        .addRuleInstance(CoreRules.JOIN_CONDITION_PUSH)
        .addRuleInstance(CoreRules.FILTER_PROJECT_TRANSPOSE)
        .addRuleInstance(CoreRules.FILTER_SET_OP_TRANSPOSE)
        .build();
  }

  @Test void testTransitiveInferenceJoin() {
    final String sql = "select 1 from sales.emp d\n"
        + "inner join sales.emp e on d.deptno = e.deptno where e.deptno > 7";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceProject() {
    final String sql = "select 1 from (select * from sales.emp where deptno > 7) d\n"
        + "inner join sales.emp e on d.deptno = e.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceAggregate() {
    final String sql = "select 1 from (select deptno, count(*) from sales.emp where deptno > 7\n"
        + "group by deptno) d inner join sales.emp e on d.deptno = e.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceUnion() {
    final String sql = "select 1 from\n"
        + "(select deptno from sales.emp where deptno > 7\n"
        + "union all select deptno from sales.emp where deptno > 10) d\n"
        + "inner join sales.emp e on d.deptno = e.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceJoin3way() {
    final String sql = "select 1 from sales.emp d\n"
        + "inner join sales.emp e on d.deptno = e.deptno\n"
        + "inner join sales.emp f on e.deptno = f.deptno\n"
        + "where d.deptno > 7";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceJoin3wayAgg() {
    final String sql = "select 1 from\n"
        + "(select deptno, count(*) from sales.emp where deptno > 7 group by deptno) d\n"
        + "inner join sales.emp e on d.deptno = e.deptno\n"
        + "inner join sales.emp f on e.deptno = f.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceLeftOuterJoin() {
    final String sql = "select 1 from sales.emp d\n"
        + "left outer join sales.emp e on d.deptno = e.deptno\n"
        + "where d.deptno > 7 and e.deptno > 9";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceRightOuterJoin() {
    final String sql = "select 1 from sales.emp d\n"
        + "right outer join sales.emp e on d.deptno = e.deptno\n"
        + "where d.deptno > 7 and e.deptno > 9";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceFullOuterJoin() {
    final String sql = "select 1 from sales.emp d full outer join sales.emp e\n"
        + "on d.deptno = e.deptno  where d.deptno > 7 and e.deptno > 9";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).checkUnchanged();
  }

  @Test void testTransitiveInferencePreventProjectPullUp() {
    final String sql = "select 1 from (select comm as deptno from sales.emp where deptno > 7) d\n"
        + "inner join sales.emp e on d.deptno = e.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).checkUnchanged();
  }

  @Test void testTransitiveInferencePullUpThruAlias() {
    final String sql = "select 1 from (select comm as deptno from sales.emp where comm > 7) d\n"
        + "inner join sales.emp e on d.deptno = e.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceConjunctInPullUp() {
    final String sql = "select 1 from sales.emp d\n"
        + "inner join sales.emp e on d.deptno = e.deptno\n"
        + "where d.deptno in (7, 9) or d.deptno > 10";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceNoPullUpExprs() {
    final String sql = "select 1 from sales.emp d\n"
        + "inner join sales.emp e on d.deptno = e.deptno\n"
        + "where d.deptno in (7, 9) or d.comm > 10";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).checkUnchanged();
  }

  @Test void testTransitiveInferenceUnion3way() {
    final String sql = "select 1 from\n"
        + "(select deptno from sales.emp where deptno > 7\n"
        + "union all\n"
        + "select deptno from sales.emp where deptno > 10\n"
        + "union all\n"
        + "select deptno from sales.emp where deptno > 1) d\n"
        + "inner join sales.emp e on d.deptno = e.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceUnion3wayOr() {
    final String sql = "select 1 from\n"
        + "(select empno, deptno from sales.emp where deptno > 7 or empno < 10\n"
        + "union all\n"
        + "select empno, deptno from sales.emp where deptno > 10 or empno < deptno\n"
        + "union all\n"
        + "select empno, deptno from sales.emp where deptno > 1) d\n"
        + "inner join sales.emp e on d.deptno = e.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-443">[CALCITE-443]
   * getPredicates from a union is not correct</a>. */
  @Test void testTransitiveInferenceUnionAlwaysTrue() {
    final String sql = "select d.deptno, e.deptno from\n"
        + "(select deptno from sales.emp where deptno < 4) d\n"
        + "inner join\n"
        + "(select deptno from sales.emp where deptno > 7\n"
        + "union all select deptno from sales.emp) e\n"
        + "on d.deptno = e.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testTransitiveInferenceConstantEquiPredicate() {
    final String sql = "select 1 from sales.emp d\n"
        + "inner join sales.emp e on d.deptno = e.deptno  where 1 = 1";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).checkUnchanged();
  }

  @Test void testTransitiveInferenceComplexPredicate() {
    final String sql = "select 1 from sales.emp d\n"
        + "inner join sales.emp e on d.deptno = e.deptno\n"
        + "where d.deptno > 7 and e.sal = e.deptno and d.comm = d.deptno\n"
        + "and d.comm + d.deptno > d.comm/2";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  @Test void testPullConstantIntoProject() {
    final String sql = "select deptno, deptno + 1, empno + deptno\n"
        + "from sales.emp\n"
        + "where deptno = 10";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES,
            CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Tests propagation of a filter derived from an "IS NOT DISTINCT FROM"
   * predicate.
   *
   * <p>By the time the rule sees the predicate, it has been converted to
   * "deptno = 10 OR (deptno IS NULL AND 10 IS NULL)", so
   * {@link #testPullConstantIntoProjectWithIsNotDistinctFromDate()} is a purer
   * test of the functionality. */
  @Test void testPullConstantIntoProjectWithIsNotDistinctFrom() {
    final String sql = "select deptno, deptno + 1, empno + deptno\n"
        + "from sales.emp\n"
        + "where deptno is not distinct from 10";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testPullConstantIntoProjectWithIsNotDistinctFromForNull() {
    final String sql = "select mgr, deptno\n"
        + "from sales.emp\n"
        + "where mgr is not distinct from null";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5336">[CALCITE-5336]
   * Infer constants from predicates with IS NOT DISTINCT FROM operator</a>.
   *
   * <p>Reduce expression rules should identify constants with
   * {@code IS NOT DISTINCT FROM} operator as they do for {@code =}. */
  @Test void testPullConstantIntoProjectWithIsNotDistinctFromDate() {
    // Build a tree equivalent to the SQL
    //   SELECT hiredate
    //   FROM emp
    //   WHERE hiredate IS NOT DISTINCT FROM DATE '2020-12-11'
    //
    // We build a tree because if we used the SQL parser, it would expand
    //   x IS NOT DISTINCT FROM y
    // to
    //   x = y OR (x IS NULL AND y IS NULL)
    final Function<RelBuilder, RelNode> relFn = b ->
        b.scan("EMP")
            .filter(
                b.getRexBuilder()
                    .makeCall(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
                        b.field(4),
                        b.literal(new DateString(2020, 12, 11))))
            .project(b.field(4))
            .build();

    relFn(relFn)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testPullConstantIntoFilter() {
    final String sql = "select * from (select * from sales.emp where deptno = 10)\n"
        + "where deptno + 5 > empno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES,
            CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testPullUpPredicatesFromUnionWithProject() {
    final String sql = "select empno = null from emp where comm = 2\n"
        + "union all\n"
        + "select comm = 2 from emp where comm = 2";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.PROJECT_REDUCE_EXPRESSIONS)
          .check();
  }

  @Test void testPullUpPredicatesFromProject1() {
    final String sql = "select comm <> 2, comm = 2 from emp where comm = 2";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testPullUpPredicatesFromProject2() {
    final String sql = "select comm = 2, empno <> 1 from emp where comm = 2 and empno = 1";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1995">[CALCITE-1995]
   * Remove predicates from Filter if they can be proved to be always true or
   * false</a>. */
  @Test void testSimplifyFilter() {
    final String sql = "select * from (select * from sales.emp where deptno > 10)\n"
        + "where empno > 3 and deptno > 5";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES,
            CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testPullConstantIntoJoin() {
    final String sql = "select * from (select * from sales.emp where empno = 10) as e\n"
        + "left join sales.dept as d on e.empno = d.deptno";
    sql(sql).withPre(getTransitiveProgram())
        .withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .check();
  }

  @Test void testPullConstantIntoJoin2() {
    final String sql = "select * from (select * from sales.emp where empno = 10) as e\n"
        + "join sales.dept as d on e.empno = d.deptno and e.deptno + e.empno = d.deptno + 5";
    final HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES)
        .addRuleCollection(
            ImmutableList.of(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
                CoreRules.FILTER_PROJECT_TRANSPOSE,
                CoreRules.JOIN_REDUCE_EXPRESSIONS))
        .build();
    sql(sql).withPre(getTransitiveProgram()).withProgram(program).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7302">[CALCITE-7302]
   * Infinite loop with JoinPushTransitivePredicatesRule</a>. */
  @Test void testInfiniteLoopWithBetweenAnd() {
    final String sql = "With dept_temp as"
        + " (SELECT deptno, name FROM dept where deptno between 30 and 50),"
        + "emp_temp as"
        + " (Select ename, deptno from emp)"
        + "select * from dept_temp inner join emp_temp on dept_temp.deptno = emp_temp.deptno ";
    sql(sql).withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2110">[CALCITE-2110]
   * ArrayIndexOutOfBoundsException in RexSimplify when using
   * ReduceExpressionsRule.JOIN_INSTANCE</a>. */
  @Test void testCorrelationScalarAggAndFilter() {
    final String sql = "SELECT e1.empno\n"
        + "FROM emp e1, dept d1 where e1.deptno = d1.deptno\n"
        + "and e1.deptno < 10 and d1.deptno < 15\n"
        + "and e1.sal > (select avg(sal) from emp e2 where e1.empno = e2.empno)";
    sql(sql)
        .withDecorrelate(true)
        .withTrim(true)
        .withExpand(true)
        .withPreRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS,
            CoreRules.FILTER_REDUCE_EXPRESSIONS,
            CoreRules.JOIN_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3111">[CALCITE-3111]
   * Allow custom implementations of Correlate in RelDecorrelator</a>. */
  @Test void testCustomDecorrelate() {
    final String sql = "SELECT e1.empno\n"
        + "FROM emp e1, dept d1 where e1.deptno = d1.deptno\n"
        + "and e1.deptno < 10 and d1.deptno < 15\n"
        + "and e1.sal > (select avg(sal) from emp e2 where e1.empno = e2.empno)";

    // Convert sql to rel
    final RelOptFixture fixture = sql(sql).withExpand(true);
    final RelNode rel = fixture.toRel();

    // Create a duplicate rel tree with a CustomCorrelate instead of
    // LogicalCorrelate.
    final LogicalCorrelate logicalCorrelate =
        (LogicalCorrelate) rel.getInput(0).getInput(0);
    CustomCorrelate customCorrelate =
        new CustomCorrelate(logicalCorrelate.getCluster(),
            logicalCorrelate.getTraitSet(),
            logicalCorrelate.getHints(),
            logicalCorrelate.getLeft(),
            logicalCorrelate.getRight(),
            logicalCorrelate.getCorrelationId(),
            logicalCorrelate.getRequiredColumns(),
            logicalCorrelate.getJoinType());
    RelNode newRoot =
        rel.copy(rel.getTraitSet(),
            ImmutableList.of(
                rel.getInput(0).copy(rel.getInput(0).getTraitSet(),
                    ImmutableList.of(customCorrelate))));

    // Decorrelate both trees using the same relBuilder
    final RelBuilder relBuilder = RelBuilder.create(RelBuilderTest.config().build());
    RelNode logicalDecorrelated = RelDecorrelator.decorrelateQuery(rel, relBuilder);
    RelNode customDecorrelated = RelDecorrelator.decorrelateQuery(newRoot, relBuilder);
    String logicalDecorrelatedPlan = NL + RelOptUtil.toString(logicalDecorrelated);
    String customDecorrelatedPlan = NL + RelOptUtil.toString(customDecorrelated);

    // Ensure that the plans are equal
    fixture.diffRepos.assertEquals(
        "Comparing Plans from LogicalCorrelate and CustomCorrelate",
        logicalDecorrelatedPlan, customDecorrelatedPlan);
  }

  @Test void testProjectWindowTransposeRule() {
    final String sql = "select count(empno) over(), deptno from emp";
    sql(sql)
        .withRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW,
            CoreRules.PROJECT_WINDOW_TRANSPOSE)
        .check();
  }

  @Test void testProjectWindowTransposeRuleWithConstants() {
    final String sql = "select col1, col2\n"
        + "from (\n"
        + "  select empno,\n"
        + "    sum(100) over (partition by  deptno order by sal) as col1,\n"
        + "  sum(1000) over(partition by deptno order by sal) as col2\n"
        + "  from emp)";
    sql(sql)
        .withRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW,
            CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_WINDOW_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6632">[CALCITE-6632]
   * Wrong optimization because window missing constants in digest</a>. */
  @Test void testWindowMissingConstantInDigest() {
    final String sql = "select sum(100) over (partition by deptno order by sal) as s\n"
        + "from emp\n"
        + "union all\n"
        + "select sum(1000) over(partition by deptno order by sal) as s\n"
        + "from emp\n";
    sql(sql)
        .withRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW)
        .check();
  }

  /** While it's probably valid relational algebra for a Project to contain
   * a RexOver inside a RexOver, ProjectMergeRule should not bring it about. */
  @Test void testProjectMergeShouldIgnoreOver() {
    final String sql = "select row_number() over (order by deptno), col1\n"
        + "from (\n"
        + "  select deptno,\n"
        + "    sum(100) over (partition by  deptno order by sal) as col1\n"
        + "  from emp)";
    sql(sql).withRule(CoreRules.PROJECT_MERGE).checkUnchanged();
  }

  @Test void testAggregateProjectPullUpConstants() {
    final String sql = "select job, empno, sal, sum(sal) as s\n"
        + "from emp where empno = 10\n"
        + "group by job, empno, sal";
    sql(sql).withRule(CoreRules.AGGREGATE_ANY_PULL_UP_CONSTANTS).check();
  }

  @Test void testAggregateProjectPullUpConstants2() {
    final String sql = "select ename, sal\n"
        + "from (select '1', ename, sal from emp where ename = 'John') subq\n"
        + "group by ename, sal";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_ANY_PULL_UP_CONSTANTS)
        .check();
  }

  @Test void testPushFilterWithRank() {
    final String sql = "select e1.ename, r\n"
        + "from (\n"
        + "  select ename, "
        + "  rank() over(partition by  deptno order by sal) as r "
        + "  from emp) e1\n"
        + "where r < 2";
    sql(sql).withRule(CoreRules.FILTER_PROJECT_TRANSPOSE)
        .checkUnchanged();
  }

  @Test void testPushFilterWithRankExpr() {
    final String sql = "select e1.ename, r\n"
        + "from (\n"
        + "  select ename,\n"
        + "  rank() over(partition by  deptno order by sal) + 1 as r "
        + "  from emp) e1\n"
        + "where r < 2";
    sql(sql).withRule(CoreRules.FILTER_PROJECT_TRANSPOSE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-841">[CALCITE-841]
   * Redundant windows when window function arguments are expressions</a>. */
  @Test void testExpressionInWindowFunction() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ProjectToWindowRule.class);

    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW);

    final String sql = "select\n"
        + " sum(deptno) over(partition by deptno order by sal) as sum1,\n"
        + "sum(deptno + sal) over(partition by deptno order by sal) as sum2\n"
        + "from emp";
    sql(sql)
        .withPlanner(hepPlanner)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-888">[CALCITE-888]
   * Overlay window loses PARTITION BY list</a>. */
  @Test void testWindowInParenthesis() {
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleClass(ProjectToWindowRule.class);
    HepPlanner hepPlanner = new HepPlanner(builder.build());
    hepPlanner.addRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW);

    final String sql = "select count(*) over (w), count(*) over w\n"
        + "from emp\n"
        + "window w as (partition by empno order by empno)";
    sql(sql)
        .withPlanner(hepPlanner)
        .check();
  }

  /** Test case for DX-11490:
   * Make sure the planner doesn't fail over wrong push down
   * of is null. */
  @Test void testIsNullPushDown() {
    HepProgramBuilder preBuilder = new HepProgramBuilder();
    preBuilder.addRuleInstance(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW);

    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleInstance(CoreRules.PROJECT_REDUCE_EXPRESSIONS);
    builder.addRuleInstance(CoreRules.FILTER_REDUCE_EXPRESSIONS);
    HepPlanner hepPlanner = new HepPlanner(builder.build());

    final String sql = "select empno, deptno, w_count from (\n"
        + "  select empno, deptno, count(empno) over (w) w_count\n"
        + "  from emp\n"
        + "  window w as (partition by deptno order by empno)\n"
        + ") sub_query where w_count is null";
    sql(sql)
        .withPre(preBuilder.build())
        .withPlanner(hepPlanner)
        .check();
  }

  @Test void testIsNullPushDown2() {
    HepProgramBuilder preBuilder = new HepProgramBuilder();
    preBuilder.addRuleInstance(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW);

    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleInstance(CoreRules.PROJECT_REDUCE_EXPRESSIONS);
    builder.addRuleInstance(CoreRules.FILTER_REDUCE_EXPRESSIONS);
    HepPlanner hepPlanner = new HepPlanner(builder.build());

    final String sql = "select empno, deptno, w_count from (\n"
        + "  select empno, deptno, count(empno) over (ROWS BETWEEN 10 PRECEDING AND 1 PRECEDING) w_count\n"
        + "  from emp\n"
        + ") sub_query where w_count is null";
    sql(sql)
        .withPre(preBuilder.build())
        .withPlanner(hepPlanner)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5861">
   * [CALCITE-5861] ReduceExpressionsRule rules should constant-fold
   * expressions in window bounds</a>.
   */
  @Test void testExpressionPreceding() {
    HepProgramBuilder preBuilder = new HepProgramBuilder();
    preBuilder.addRuleInstance(CoreRules.PROJECT_REDUCE_EXPRESSIONS);
    preBuilder.addRuleInstance(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW);

    final String sql =
        "select COUNT(*) over (\n"
            + "ORDER BY empno\n"
            + "ROWS BETWEEN 5 + 5 PRECEDING AND 1 PRECEDING) AS w_count from emp\n";
    sql(sql)
        .withPre(preBuilder.build())
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-750">[CALCITE-750]
   * Allow windowed aggregate on top of regular aggregate</a>. */
  @Test void testNestedAggregates() {
    final String sql = "SELECT\n"
        + "  avg(sum(sal) + 2 * min(empno) + 3 * avg(empno))\n"
        + "  over (partition by deptno)\n"
        + "from emp\n"
        + "group by deptno";
    sql(sql).withRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2078">[CALCITE-2078]
   * Aggregate functions in OVER clause</a>. */
  @Test void testWindowFunctionOnAggregations() {
    final String sql = "SELECT\n"
        + "  min(empno),\n"
        + "  sum(sal),\n"
        + "  sum(sum(sal))\n"
        + "    over (partition by min(empno) order by sum(sal))\n"
        + "from emp\n"
        + "group by deptno";
    sql(sql).withRule(CoreRules.PROJECT_TO_LOGICAL_PROJECT_AND_WINDOW).check();
  }

  @Test void testMeasureSort() {
    final String sql = "select deptno, c1\n"
        + "from (select deptno, job, count(*) + 1 as measure c1\n"
        + "  from emp)\n"
        + "order by deptno";
    sql(sql)
        .withRule(MeasureRules.PROJECT_SORT)
        .check();
  }

  @Test void testMeasureAggregate() {
    final String sql = "select deptno, c1\n"
        + "from (select deptno, job, count(*) + 1 as measure c1\n"
        + "  from emp)\n"
        + "group by deptno";
    sql(sql)
        .withRule(MeasureRules.AGGREGATE,
            CoreRules.PROJECT_MERGE,
            MeasureRules.PROJECT)
        .check();
  }

  /** Tests use of a measure in a non-aggregate query. Calls
   * {@link RelOptFixture#checkUnchanged()} because Sql-to-rel has done the
   * necessary work already. */
  @Test void testMeasureWithoutGroupBy() {
    final String sql = "with empm as\n"
        + "  (select *, avg(sal) as measure avgSal from emp)\n"
        + "select deptno, avgSal\n"
        + "from empm";
    sql(sql)
        .withRule(MeasureRules.AGGREGATE2,
            CoreRules.PROJECT_MERGE,
            MeasureRules.PROJECT)
        .checkUnchanged();
  }

  @Test void testMeasureWithoutGroupByWithOrderBy() {
    final String sql = "with empm as\n"
        + "  (select *, avg(sal) as measure avgSal from emp)\n"
        + "select deptno, avgSal\n"
        + "from empm\n"
        + "order by 2 desc limit 3";
    sql(sql)
        .withRule(MeasureRules.PROJECT_SORT,
            CoreRules.PROJECT_MERGE,
            MeasureRules.PROJECT)
        .check();
  }

  @Disabled
  @Test void testMeasureJoin() {
    final String sql = "with deptm as\n"
        + "  (select deptno, name, avg(char_length(name)) as measure m\n"
        + "   from dept)\n"
        + "select deptno, aggregate(m) as m\n"
        + "from deptm join emp using (deptno)\n"
        + "group by deptno";
    sql(sql)
        .withFactory(t ->
            t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.STANDARD, SqlLibrary.CALCITE))) // for AGGREGATE
        .withRule(MeasureRules.AGGREGATE,
            CoreRules.PROJECT_MERGE,
            MeasureRules.PROJECT)
        .check();
  }

  @Test void testPushAggregateThroughJoin1() {
    final String sql = "select e.job,d.name\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "join sales.dept as d on e.job = d.name\n"
        + "group by e.job,d.name";
    sql(sql).withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by on non-join keys, group by on
   * non-null generating side only. */
  @Test void testPushAggregateThroughOuterJoin1() {
    final String sql = "select e.ename\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.dept as d on e.job = d.name\n"
        + "group by e.ename";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by on non-join keys, on null
   * generating side only. */
  @Test void testPushAggregateThroughOuterJoin2() {
    final String sql = "select d.ename\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.emp as d on e.job = d.job\n"
        + "group by d.ename";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by on both side on non-join
   * keys. */
  @Test void testPushAggregateThroughOuterJoin3() {
    final String sql = "select e.ename, d.mgr\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.emp as d on e.job = d.job\n"
        + "group by e.ename,d.mgr";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by on key same as join key,
   * group by on non-null generating side. */
  @Test void testPushAggregateThroughOuterJoin4() {
    final String sql = "select e.job\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.dept as d on e.job = d.name\n"
        + "group by e.job";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by on key same as join key,
   * group by on null generating side. */
  @Test void testPushAggregateThroughOuterJoin5() {
    final String sql = "select d.name\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.dept as d on e.job = d.name\n"
        + "group by d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by on key same as join key,
   * group by on both side. */
  @Test void testPushAggregateThroughOuterJoin6() {
    final String sql = "select e.job,d.name\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.dept as d on e.job = d.name\n"
        + "group by e.job,d.name";
    sql(sql).withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by key is susbset of join keys,
   * group by on non-null generating side. */
  @Test void testPushAggregateThroughOuterJoin7() {
    final String sql = "select e.job\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.dept as d on e.job = d.name\n"
        + "and e.deptno + e.empno = d.deptno + 5\n"
        + "group by e.job";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by key is a subset of join keys,
   * group by on null generating side. */
  @Test void testPushAggregateThroughOuterJoin8() {
    final String sql = "select d.name\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.dept as d on e.job = d.name\n"
        + "and e.deptno + e.empno = d.deptno + 5\n"
        + "group by d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, group by key is susbset of join keys,
   * group by on both sides. */
  @Test void testPushAggregateThroughOuterJoin9() {
    final String sql = "select e.job, d.name\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "left outer join sales.dept as d on e.job = d.name\n"
        + "and e.deptno + e.empno = d.deptno + 5\n"
        + "group by e.job, d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for outer join, with aggregate functions. */
  @Test void testPushAggregateThroughOuterJoin10() {
    final String sql = "select count(e.ename)\n"
        + "from (select * from sales.emp where empno = 10) as e\n"
        + "left outer join sales.emp as d on e.job = d.job\n"
        + "group by e.ename,d.mgr";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .checkUnchanged();
  }

  /** Test case for non-equi outer join. */
  @Test void testPushAggregateThroughOuterJoin11() {
    final String sql = "select e.empno,d.deptno\n"
        + "from (select * from sales.emp where empno = 10) as e\n"
        + "left outer join sales.dept as d on e.empno < d.deptno\n"
        + "group by e.empno,d.deptno";
    sql(sql)
        .withRelBuilderConfig(b -> b.withAggregateUnique(true))
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5879">
   * AssertionError during constant reduction of SPLIT expression that returns NULL</a>. */
  @Test public void testSplitNull() {
    final String query = "select split('1|2|3', NULL)";
    sql(query)
        .withFactory(
            t -> t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.BIG_QUERY))) // needed for SPLIT function
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5879">
   * AssertionError during constant reduction of SPLIT expression that returns NULL</a>. */
  @Test public void testSplitNull1() {
    final String query = "select split(NULL, '|')";
    sql(query)
        .withFactory(
            t -> t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.BIG_QUERY))) // needed for SPLIT function
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5879">
   * AssertionError during constant reduction of SPLIT expression that returns NULL</a>. */
  @Test public void testSplitNull2() {
    final String query = "select split(NULL, NULL)";
    sql(query)
        .withFactory(
            t -> t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.BIG_QUERY))) // needed for SPLIT function
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5882">
   * [CALCITE-5882] Compile-time evaluation of SPLIT function returns incorrect result</a>. */
  @Test public void testSplit() {
    final String query = "select split('1|2|3', '|')";
    sql(query)
        .withFactory(
            t -> t.withOperatorTable(opTab ->
                SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
                    SqlLibrary.BIG_QUERY))) // needed for SPLIT function
        .withRule(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for right outer join, group by on key same as join
   * key, group by on (left)null generating side. */
  @Test void testPushAggregateThroughOuterJoin12() {
    final String sql = "select e.job\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "right outer join sales.dept as d on e.job = d.name\n"
        + "group by e.job";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for full outer join, group by on key same as join key,
   * group by on one side. */
  @Test void testPushAggregateThroughOuterJoin13() {
    final String sql = "select e.job\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "full outer join sales.dept as d on e.job = d.name\n"
        + "group by e.job";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for full outer join, group by on key same as join key,
   * group by on both side. */
  @Test void testPushAggregateThroughOuterJoin14() {
    final String sql = "select e.mgr, d.mgr\n"
        + "from sales.emp as e\n"
        + "full outer join sales.emp as d on e.mgr = d.mgr\n"
        + "group by d.mgr, e.mgr";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for full outer join, group by on both side on non-join
   * keys. */
  @Test void testPushAggregateThroughOuterJoin15() {
    final String sql = "select e.ename, d.mgr\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "full outer join sales.emp as d on e.job = d.job\n"
        + "group by e.ename,d.mgr";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for full outer join, group by key is susbset of join
   * keys. */
  @Test void testPushAggregateThroughOuterJoin16() {
    final String sql = "select e.job\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "full outer join sales.dept as d on e.job = d.name\n"
        + "and e.deptno + e.empno = d.deptno + 5\n"
        + "group by e.job";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  @Test void testPushAggregateThroughJoin2() {
    final String sql = "select e.job,d.name\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "join sales.dept as d on e.job = d.name\n"
        + "and e.deptno + e.empno = d.deptno + 5\n"
        + "group by e.job,d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  @Test void testPushAggregateThroughJoin3() {
    final String sql = "select e.empno,d.deptno\n"
        + "from (select * from sales.emp where empno = 10) as e\n"
        + "join sales.dept as d on e.empno < d.deptno\n"
        + "group by e.empno,d.deptno";
    sql(sql)
        .withRelBuilderConfig(b -> b.withAggregateUnique(true))
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1544">[CALCITE-1544]
   * AggregateJoinTransposeRule fails to preserve row type</a>. */
  @Test void testPushAggregateThroughJoin4() {
    final String sql = "select e.deptno\n"
        + "from sales.emp as e join sales.dept as d on e.deptno = d.deptno\n"
        + "group by e.deptno";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  @Test void testPushAggregateThroughJoin5() {
    final String sql = "select e.deptno, d.deptno\n"
        + "from sales.emp as e join sales.dept as d on e.deptno = d.deptno\n"
        + "group by e.deptno, d.deptno";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2200">[CALCITE-2200]
   * Infinite loop for JoinPushTransitivePredicatesRule</a>. */
  @Test void testJoinPushTransitivePredicatesRule() {
    final String sql = "select d.deptno from sales.emp d where d.deptno\n"
        + "IN (select e.deptno from sales.emp e "
        + "where e.deptno = d.deptno or e.deptno = 4)";
    sql(sql)
        .withExpand(true)
        .withPreRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES)
        .withRule() // empty program
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2205">[CALCITE-2205]
   * One more infinite loop for JoinPushTransitivePredicatesRule</a>. */
  @Test void testJoinPushTransitivePredicatesRule2() {
    final String sql = "select n1.SAL\n"
        + "from EMPNULLABLES_20 n1\n"
        + "where n1.SAL IN (\n"
        + "  select n2.SAL\n"
        + "  from EMPNULLABLES_20 n2\n"
        + "  where n1.SAL = n2.SAL or n1.SAL = 4)";
    sql(sql).withDecorrelate(true)
        .withExpand(true)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5387">[CALCITE-5387]
   * Type-mismatch on nullability in JoinPushTransitivePredicatesRule RelRule</a>. */
  @Test void testJoinPushTransitivePredicatesNullabilityIssue() {
    final String sql = "WITH\n"
        + "non_null_table AS (\n"
        + "  SELECT DATE '2023-08-07' AS date_col_non_null FROM dept\n"
        + "),\n"
        + "null_table AS (\n"
        + "  SELECT CAST(null as DATE) AS date_col_null FROM dept\n"
        + ")\n"
        + "SELECT *\n"
        + "FROM non_null_table\n"
        + "JOIN null_table\n"
        + "ON null_table.date_col_null = non_null_table.date_col_non_null";

    sql(sql).withRule(CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES).check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6432">[CALCITE-6432]
   * Infinite loop for JoinPushTransitivePredicatesRule</a>. */
  @Test void testProjectPredicatePull() {
    final String sql = "select e.ename, d.dname\n"
        + "from (select ename, deptno from emp where deptno = 10) e\n"
        + "join (select name dname, deptno, * from dept) d\n"
        + "on e.deptno = d.deptno";
    final HepProgram program = new HepProgramBuilder()
        .addRuleCollection(
            ImmutableList.of(CoreRules.FILTER_PROJECT_TRANSPOSE,
                CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES))
        .build();
    sql(sql).withProgram(program).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2275">[CALCITE-2275]
   * JoinPushTransitivePredicatesRule wrongly pushes down NOT condition</a>. */
  @Test void testInferringPredicatesWithNotOperatorInJoinCondition() {
    final String sql = "select * from sales.emp d\n"
        + "join sales.emp e on e.deptno = d.deptno and d.deptno not in (4, 6)";
    sql(sql)
        .withRelBuilderSimplify(false)
        .withDecorrelate(true)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_CONDITION_PUSH,
            CoreRules.JOIN_PUSH_TRANSITIVE_PREDICATES)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2195">[CALCITE-2195]
   * AggregateJoinTransposeRule fails to aggregate over unique column</a>. */
  @Test void testPushAggregateThroughJoin6() {
    final String sql = "select sum(B.sal)\n"
        + "from sales.emp as A\n"
        + "join (select distinct sal from sales.emp) as B\n"
        + "on A.sal=B.sal\n";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  @Test void testPushAggregateThroughJoin7() {
    final String sql = "select any_value(distinct B.sal)\n"
        + "from sales.emp as A\n"
        + "join (select distinct sal from sales.emp) as B\n"
        + "on A.sal=B.sal\n";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  @Test void testPushAggregateThroughJoin8() {
    final String sql = "select single_value(distinct B.sal)\n"
        + "from sales.emp as A\n"
        + "join (select distinct sal from sales.emp) as B\n"
        + "on A.sal=B.sal\n";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2278">[CALCITE-2278]
   * AggregateJoinTransposeRule fails to split aggregate call if input contains
   * an aggregate call and has distinct rows</a>. */
  @Test void testPushAggregateThroughJoinWithUniqueInput() {
    final String sql = "select A.job, B.mgr, A.deptno,\n"
        + "max(B.hiredate1) as hiredate1, sum(B.comm1) as comm1\n"
        + "from sales.emp as A\n"
        + "join (select mgr, sal, max(hiredate) as hiredate1,\n"
        + "    sum(comm) as comm1 from sales.emp group by mgr, sal) as B\n"
        + "on A.sal=B.sal\n"
        + "group by A.job, B.mgr, A.deptno";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** SUM is the easiest aggregate function to split. */
  @Test void testPushAggregateSumThroughJoin() {
    final String sql = "select e.job,sum(sal)\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "join sales.dept as d on e.job = d.name\n"
        + "group by e.job,d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2105">[CALCITE-2105]
   * AggregateJoinTransposeRule incorrectly makes a SUM NOT NULL when Aggregate
   * has no group keys</a>. */
  @Test void testPushAggregateSumWithoutGroupKeyThroughJoin() {
    final String sql = "select sum(sal)\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "join sales.dept as d on e.job = d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2108">[CALCITE-2108]
   * AggregateJoinTransposeRule incorrectly splits a SUM0 call when Aggregate
   * has no group keys</a>.
   *
   * <p>Similar to {@link #testPushAggregateSumThroughJoin()},
   * but also uses {@link AggregateReduceFunctionsRule}. */
  @Test void testPushAggregateSumThroughJoinAfterAggregateReduce() {
    final String sql = "select sum(sal)\n"
        + "from (select * from sales.emp where ename = 'A') as e\n"
        + "join sales.dept as d on e.job = d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_REDUCE_FUNCTIONS,
            CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Push a variety of aggregate functions. */
  @Test void testPushAggregateFunctionsThroughJoin() {
    final String sql = "select e.job,\n"
        + "  min(sal) as min_sal, min(e.deptno) as min_deptno,\n"
        + "  sum(sal) + 1 as sum_sal_plus, max(sal) as max_sal,\n"
        + "  sum(sal) as sum_sal_2, count(sal) as count_sal,\n"
        + "  count(mgr) as count_mgr\n"
        + "from sales.emp as e\n"
        + "join sales.dept as d on e.job = d.name\n"
        + "group by e.job,d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Push a aggregate functions into a relation that is unique on the join
   * key. */
  @Test void testPushAggregateThroughJoinDistinct() {
    final String sql = "select d.name,\n"
        + "  sum(sal) as sum_sal, count(*) as c\n"
        + "from sales.emp as e\n"
        + "join (select distinct name from sales.dept) as d\n"
        + "  on e.job = d.name\n"
        + "group by d.name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6600">[CALCITE-6600]
   * AggregateJoinTransposeRule can throw ArrayIndexOutOfBoundsException when applied
   * on a SemiJoin</a>.*/
  @Test void testPushAggregateThroughSemiJoin() {
    final String sql = "select distinct sal\n"
        + "from (select * from sales.emp e where e.job in\n"
        + "(select d.name from sales.dept d))";
    sql(sql)
        .withLateDecorrelate(true)
        .withTrim(true)
        .withPreRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withRule(CoreRules.PROJECT_MERGE,
            CoreRules.PROJECT_TO_SEMI_JOIN,
            CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Push count(*) through join, no GROUP BY. */
  @Test void testPushAggregateSumNoGroup() {
    final String sql =
        "select count(*) from sales.emp join sales.dept on job = name";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3076">[CALCITE-3076]
   * AggregateJoinTransposeRule throws error for unique under aggregate keys when
   * generating merged calls</a>.*/
  @Test void testPushAggregateThroughJoinOnEmptyLogicalValues() {
    final String sql = "select count(*) volume, sum(C1.sal) C1_sum_sal "
        + "from (select sal, ename from sales.emp where 1=2) C1 "
        + "inner join (select ename from sales.emp) C2   "
        + "on C1.ename = C2.ename ";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2249">[CALCITE-2249]
   * AggregateJoinTransposeRule generates non-equivalent nodes if Aggregate
   * contains DISTINCT aggregate function</a>. */
  @Test void testPushDistinctAggregateIntoJoin() {
    final String sql = "select count(distinct sal) from sales.emp\n"
        + " join sales.dept on job = name";
    sql(sql).withRule(CoreRules.AGGREGATE_JOIN_TRANSPOSE_EXTENDED)
        .checkUnchanged();
  }

  /** Tests that ProjectAggregateMergeRule removes unused aggregate calls but
   * not group keys. */
  @Test void testProjectAggregateMerge() {
    final String sql = "select deptno + ss\n"
        + "from (\n"
        + "  select job, deptno, min(sal) as ms, sum(sal) as ss\n"
        + "  from sales.emp\n"
        + "  group by job, deptno)";
    sql(sql).withRule(CoreRules.PROJECT_AGGREGATE_MERGE)
        .check();
  }

  /** Tests that ProjectAggregateMergeRule does nothing when all aggregate calls
   * are referenced. */
  @Test void testProjectAggregateMergeNoOp() {
    final String sql = "select deptno + ss + ms\n"
        + "from (\n"
        + "  select job, deptno, min(sal) as ms, sum(sal) as ss\n"
        + "  from sales.emp\n"
        + "  group by job, deptno)";
    sql(sql).withRule(CoreRules.PROJECT_AGGREGATE_MERGE)
        .checkUnchanged();
  }

  /** Tests that ProjectAggregateMergeRule converts {@code COALESCE(SUM(x), 0)}
   * into {@code SUM0(x)}. */
  @Test void testProjectAggregateMergeSum0() {
    final String sql = "select coalesce(sum_sal, 0) as ss0\n"
        + "from (\n"
        + "  select sum(sal) as sum_sal\n"
        + "  from sales.emp)";
    sql(sql).withRule(CoreRules.PROJECT_AGGREGATE_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6834">[CALCITE-6834]
   * In query that applies COALESCE to nullable SUM, EnumerableProjectToCalcRule
   * throws AssertionError</a>. */
  @Test void testProjectAggregateMergeSum01() {
    final String sql = "select coalesce(sum(cast(mgr as tinyint)), 0) as ss0\n"
        + "from sales.emp";
    sql(sql).withRule(CoreRules.PROJECT_AGGREGATE_MERGE)
        .check();
  }

  /** As {@link #testProjectAggregateMergeSum0()} but there is another use of
   * {@code SUM} that cannot be converted to {@code SUM0}. */
  @Test void testProjectAggregateMergeSum0AndSum() {
    final String sql = "select sum_sal * 2, coalesce(sum_sal, 0) as ss0\n"
        + "from (\n"
        + "  select sum(sal) as sum_sal\n"
        + "  from sales.emp)";
    sql(sql).withRule(CoreRules.PROJECT_AGGREGATE_MERGE)
        .check();
  }

  /** Tests that ProjectAggregateMergeRule does nothing with non-numeric literals
   * and does not throw an exception. */
  @Test void testProjectAggregateMergeNonNumericLiteral() {
    // Requires a NULLABLE column to trigger
    final SqlTestFactory.CatalogReaderFactory catalogReaderFactory = (typeFactory, caseSensitive) ->
        new MockCatalogReader(typeFactory, caseSensitive) {
          @Override public MockCatalogReader init() {
            MockSchema schema = new MockSchema("SALES");
            registerSchema(schema);
            final boolean nullable = true;
            final RelDataType timestampType =
                typeFactory.createTypeWithNullability(
                    typeFactory.createSqlType(SqlTypeName.TIMESTAMP),
                    nullable);
            String tableName = "NULLABLE";
            MockTable table = MockTable
                .create(this, schema, tableName, false, 100);
            table.addColumn("HIREDATE", timestampType);
            registerTable(table);
            return this;
          }
        }.init();
    final String sql = "select hiredate, coalesce(hiredate, {ts '1969-12-31 00:00:00'}) as c1\n"
        + "from sales.nullable\n"
        + "group by hiredate";
    sql(sql)
        .withCatalogReaderFactory(catalogReaderFactory)
        .withRule(CoreRules.PROJECT_AGGREGATE_MERGE)
        .checkUnchanged();
  }

  @Test void testProjectAggregateMergeNoOpForNonSum() {
    final String sql = "select coalesce(m, 0)\n"
        + "from (\n"
        + "  select max(deptno) as m\n"
        + "  from sales.emp\n"
        + ")";
    sql(sql)
        .withRule(CoreRules.PROJECT_AGGREGATE_MERGE)
        .checkUnchanged();
  }

  /**
   * Test case for AggregateMergeRule, should merge 2 aggregates
   * into a single aggregate.
   */
  @Test void testAggregateMerge1() {
    final String sql = "select deptno c, min(y), max(z) z,\n"
        + "sum(r), sum(m) n, sum(x) sal from (\n"
        + "   select deptno, ename, sum(sal) x, max(sal) z,\n"
        + "      min(sal) y, count(hiredate) m, count(mgr) r\n"
        + "   from sales.emp group by deptno, ename) t\n"
        + "group by deptno";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .check();
  }

  /**
   * Test case for AggregateMergeRule, should merge 2 aggregates
   * into a single aggregate, top aggregate is not simple aggregate.
   */
  @Test void testAggregateMerge2() {
    final String sql = "select deptno, empno, sum(x), sum(y)\n"
        + "from (\n"
        + "  select ename, empno, deptno, sum(sal) x, count(mgr) y\n"
        + "    from sales.emp\n"
        + "  group by deptno, ename, empno) t\n"
        + "group by grouping sets(deptno, empno)";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .check();
  }

  /**
   * Test case for AggregateMergeRule, should not merge 2 aggregates
   * into a single aggregate, since lower aggregate is not simple aggregate.
   */
  @Test void testAggregateMerge3() {
    final String sql = "select deptno, sum(x) from (\n"
        + " select ename, deptno, sum(sal) x from\n"
        + "   sales.emp group by cube(deptno, ename)) t\n"
        + "group by deptno";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .checkUnchanged();
  }

  /**
   * Test case for AggregateMergeRule, should not merge 2 aggregates
   * into a single aggregate, since it contains distinct aggregate
   * function.
   */
  @Test void testAggregateMerge4() {
    final String sql = "select deptno, sum(x) from (\n"
        + "  select ename, deptno, count(distinct sal) x\n"
        + "    from sales.emp group by deptno, ename) t\n"
        + "group by deptno";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .checkUnchanged();
  }

  /**
   * Test case for AggregateMergeRule, should not merge 2 aggregates
   * into a single aggregate, since AVG doesn't support splitting.
   */
  @Test void testAggregateMerge5() {
    final String sql = "select deptno, avg(x) from (\n"
        + "  select mgr, deptno, avg(sal) x from\n"
        + "    sales.emp group by deptno, mgr) t\n"
        + "group by deptno";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .checkUnchanged();
  }

  /**
   * Test case for AggregateMergeRule, should not merge 2 aggregates
   * into a single aggregate, since top agg has no group key, and
   * lower agg function is COUNT.
   */
  @Test void testAggregateMerge6() {
    final String sql = "select sum(x) from (\n"
        + "select mgr, deptno, count(sal) x from\n"
        + "sales.emp group by deptno, mgr) t";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3957">[CALCITE-3957]
   * AggregateMergeRule should merge SUM0 into COUNT even if GROUP BY is
   * empty</a>. (It is not valid to merge a SUM onto a SUM0 if the top GROUP BY
   * is empty.) */
  @Test void testAggregateMergeSum0() {
    final String sql = "select coalesce(sum(count_comm), 0)\n"
        + "from (\n"
        + "  select deptno, count(comm) as count_comm\n"
        + "  from sales.emp\n"
        + "  group by deptno, mgr) t";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_AGGREGATE_MERGE,
            CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_MERGE)
        .check();
  }

  /**
   * Test case for AggregateMergeRule, should not merge 2 aggregates
   * into a single aggregate, since top agg contains empty grouping set,
   * and lower agg function is COUNT.
   */
  @Test void testAggregateMerge7() {
    final String sql = "select mgr, deptno, sum(x) from (\n"
        + "  select mgr, deptno, count(sal) x from\n"
        + "    sales.emp group by deptno, mgr) t\n"
        + "group by cube(mgr, deptno)";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .checkUnchanged();
  }

  /**
   * Test case for AggregateMergeRule, should merge 2 aggregates
   * into a single aggregate, since both top and bottom aggregates
   * contains empty grouping set and they are mergable.
   */
  @Test void testAggregateMerge8() {
    final String sql = "select sum(x) x, min(y) z from (\n"
        + "  select sum(sal) x, min(sal) y from sales.emp)";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6557">[CALCITE-6557]
   * AggregateMergeRule throws 'type mismatch' AssertionError</a>. The scenario
   * has the same aggregate function (SUM) at multiple levels; the lower level
   * is NOT NULL (because of GROUP BY) and the upper level is nullable. */
  @Test void testAggregateMerge9() {
    final String sql = "SELECT sum(deptno)\n"
        + "FROM (\n"
        + "    SELECT sum(deptno) AS deptno\n"
        + "    FROM dept\n"
        + "    GROUP BY name)";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7162">[CALCITE-7162]
   * AggregateMergeRule throws 'type mismatch' AssertionError</a>. The scenario
   * has the same aggregate functions (MIN and MAX) at multiple levels; the
   * lower level is NOT NULL (because of GROUP BY) and the upper level is
   * nullable. */
  @Test void testAggregateMerge10() {
    final String sql = "SELECT min(mn), max(mx)\n"
        + "FROM (\n"
        + "    SELECT min(deptno) mn, max(deptno) mx\n"
        + "    FROM dept\n"
        + "    GROUP BY name)";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_MERGE)
        .check();
  }

  /**
   * Test case for AggregateRemoveRule, should remove aggregates since
   * empno is unique and all aggregate functions are splittable.
   */
  @Test void testAggregateRemove1() {
    final String sql = "select empno, sum(sal), min(sal), max(sal),\n"
        + " bit_and(distinct sal), bit_or(sal), count(distinct sal),\n"
        + " grouping(deptno)\n"
        + "from sales.emp\n"
        + "group by empno, deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_REMOVE,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  /**
   * Test case for AggregateRemoveRule, should remove aggregates since
   * empno is unique and there are no aggregate functions.
   */
  @Test void testAggregateRemove2() {
    final String sql = "select distinct empno, deptno from sales.emp\n";
    sql(sql)
        .withRelBuilderConfig(b -> b.withAggregateUnique(true))
        .withRule(CoreRules.AGGREGATE_REMOVE,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  /**
   * Test case for AggregateRemoveRule, should remove aggregates since
   * empno is unique and all aggregate functions are splittable. Count
   * aggregate function should be transformed to CASE function call
   * because mgr is nullable.
   */
  @Test void testAggregateRemove3() {
    final String sql = "select empno, count(mgr) "
        + "from sales.emp group by empno, deptno\n";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_REMOVE,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  /**
   * Negative test case for AggregateRemoveRule, should not
   * remove aggregate because avg is not splittable.
   */
  @Test void testAggregateRemove4() {
    final String sql = "select empno, max(sal), avg(sal) "
        + "from sales.emp group by empno, deptno\n";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_REMOVE,
            CoreRules.PROJECT_MERGE)
        .checkUnchanged();
  }

  /**
   * Negative test case for AggregateRemoveRule, should not
   * remove non-simple aggregates.
   */
  @Test void testAggregateRemove5() {
    final String sql = "select empno, deptno, sum(sal) "
        + "from sales.emp group by cube(empno, deptno)\n";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_REMOVE,
            CoreRules.PROJECT_MERGE)
        .checkUnchanged();
  }

  /**
   * Negative test case for AggregateRemoveRule, should not
   * remove aggregate because deptno is not unique.
   */
  @Test void testAggregateRemove6() {
    final String sql = "select deptno, max(sal) "
        + "from sales.emp group by deptno\n";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_REMOVE,
            CoreRules.PROJECT_MERGE)
        .checkUnchanged();
  }

  /** Tests that top Aggregate is removed. Given "deptno=100", the
   * input of top Aggregate must be already distinct by "mgr". */
  @Test void testAggregateRemove7() {
    final String sql = ""
        + "select mgr, sum(sum_sal)\n"
        + "from\n"
        + "(select mgr, deptno, sum(sal) sum_sal\n"
        + " from sales.emp\n"
        + " group by mgr, deptno)\n"
        + "where deptno=100\n"
        + "group by mgr";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_REMOVE,
            CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2712">[CALCITE-2712]
   * Should remove the left join since the aggregate has no call and
   * only uses column in the left input of the bottom join as group key</a>. */
  @Test void testAggregateJoinRemove1() {
    final String sql = "select distinct e.deptno from sales.emp e\n"
        + "left outer join sales.dept d on e.deptno = d.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()} but has aggregate
   * call with distinct. */
  @Test void testAggregateJoinRemove2() {
    final String sql = "select e.deptno, count(distinct e.job)\n"
        + "from sales.emp e\n"
        + "left outer join sales.dept d on e.deptno = d.deptno\n"
        + "group by e.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()} but should not
   * remove the left join since the aggregate uses column in the right
   * input of the bottom join. */
  @Test void testAggregateJoinRemove3() {
    final String sql = "select e.deptno, count(distinct d.name)\n"
        + "from sales.emp e\n"
        + "left outer join sales.dept d on e.deptno = d.deptno\n"
        + "group by e.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()} but right join. */
  @Test void testAggregateJoinRemove4() {
    final String sql = "select distinct d.deptno\n"
        + "from sales.emp e\n"
        + "right outer join sales.dept d on e.deptno = d.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove2()} but right join. */
  @Test void testAggregateJoinRemove5() {
    final String sql = "select d.deptno, count(distinct d.name)\n"
        + "from sales.emp e\n"
        + "right outer join sales.dept d on e.deptno = d.deptno\n"
        + "group by d.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove3()} but right join. */
  @Test void testAggregateJoinRemove6() {
    final String sql = "select d.deptno, count(distinct e.job)\n"
        + "from sales.emp e\n"
        + "right outer join sales.dept d on e.deptno = d.deptno\n"
        + "group by d.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should remove the bottom join since the aggregate has no aggregate
   * call. */
  @Test void testAggregateJoinRemove7() {
    final String sql = "SELECT distinct e.deptno\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
        + "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_JOIN_REMOVE)
        .check();
  }


  /** Similar to {@link #testAggregateJoinRemove7()} but has aggregate
   * call. */
  @Test void testAggregateJoinRemove8() {
    final String sql = "SELECT e.deptno, COUNT(DISTINCT d2.name)\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
        + "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno\n"
        + "GROUP BY e.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove7()} but use columns in
   * the right input of the top join. */
  @Test void testAggregateJoinRemove9() {
    final String sql = "SELECT distinct e.deptno, d2.name\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
        + "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should not remove the bottom join since the aggregate uses column in the
   * right input of bottom join. */
  @Test void testAggregateJoinRemove10() {
    final String sql = "SELECT e.deptno, COUNT(DISTINCT d1.name, d2.name)\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
        + "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno\n"
        + "GROUP BY e.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove3()} but with agg call
   * referencing the last column of the left input. */
  @Test void testAggregateJoinRemove11() {
    final String sql = "select e.deptno, count(distinct e.slacker)\n"
        + "from sales.emp e\n"
        + "left outer join sales.dept d on e.deptno = d.deptno\n"
        + "group by e.deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_PROJECT_MERGE,
            CoreRules.AGGREGATE_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should remove the bottom join since the project uses column in the
   * right input of bottom join. */
  @Test void testProjectJoinRemove1() {
    final String sql = "SELECT e.deptno, d2.deptno\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
        + "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should not remove the bottom join since the project uses column in the
   * left input of bottom join. */
  @Test void testProjectJoinRemove2() {
    final String sql = "SELECT e.deptno, d1.deptno\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d1 ON e.deptno = d1.deptno\n"
        + "LEFT JOIN sales.dept d2 ON e.deptno = d2.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_JOIN_REMOVE)
        .checkUnchanged();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should not remove the bottom join since the right join keys of bottom
   * join are not unique. */
  @Test void testProjectJoinRemove3() {
    final String sql = "SELECT e1.deptno, d.deptno\n"
        + "FROM sales.emp e1\n"
        + "LEFT JOIN sales.emp e2 ON e1.deptno = e2.deptno\n"
        + "LEFT JOIN sales.dept d ON e1.deptno = d.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_JOIN_REMOVE)
        .checkUnchanged();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should remove the left join since the join key of the right input is
   * unique. */
  @Test void testProjectJoinRemove4() {
    final String sql = "SELECT e.deptno\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d ON e.deptno = d.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should not remove the left join since the join key of the right input is
   * not unique. */
  @Test void testProjectJoinRemove5() {
    final String sql = "SELECT e1.deptno\n"
        + "FROM sales.emp e1\n"
        + "LEFT JOIN sales.emp e2 ON e1.deptno = e2.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_REMOVE)
        .checkUnchanged();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should not remove the left join since the project use columns in the right
   * input of the join. */
  @Test void testProjectJoinRemove6() {
    final String sql = "SELECT e.deptno, d.name\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d ON e.deptno = d.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_REMOVE)
        .checkUnchanged();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should remove the right join since the join key of the left input is
   * unique. */
  @Test void testProjectJoinRemove7() {
    final String sql = "SELECT e.deptno\n"
        + "FROM sales.dept d\n"
        + "RIGHT JOIN sales.emp e ON e.deptno = d.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_REMOVE)
        .check();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should not remove the right join since the join key of the left input is
   * not unique. */
  @Test void testProjectJoinRemove8() {
    final String sql = "SELECT e2.deptno\n"
        + "FROM sales.emp e1\n"
        + "RIGHT JOIN sales.emp e2 ON e1.deptno = e2.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_REMOVE)
        .checkUnchanged();
  }

  /** Similar to {@link #testAggregateJoinRemove1()};
   * Should not remove the right join since the project uses columns in the
   * left input of the join. */
  @Test void testProjectJoinRemove9() {
    final String sql = "SELECT e.deptno, d.name\n"
        + "FROM sales.dept d\n"
        + "RIGHT JOIN sales.emp e ON e.deptno = d.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_REMOVE)
        .checkUnchanged();
  }

  /** Similar to {@link #testAggregateJoinRemove4()};
   * The project references the last column of the left input.
   * The rule should be fired.*/
  @Test void testProjectJoinRemove10() {
    final String sql = "SELECT e.deptno, e.slacker\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d ON e.deptno = d.deptno";
    sql(sql).withRule(CoreRules.PROJECT_JOIN_REMOVE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7071">[CALCITE-7071]
   * Add test for replacing JOIN node with its child node
   * when JOIN condition is false</a>. */
  @Test void testLeftJoinRemoveConditionIsAlwaysFalse() {
    final String sql = "SELECT e.deptno\n"
        + "FROM sales.emp e\n"
        + "LEFT JOIN sales.dept d ON e.deptno = d.deptno and e.deptno between 3 and 1";
    sql(sql).withRule(CoreRules.JOIN_CONDITION_PUSH,
            PruneEmptyRules.JOIN_RIGHT_INSTANCE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7071">[CALCITE-7071]
   * Add test for replacing JOIN node with its child node
   * when JOIN condition is false</a>. */
  @Test void testRightJoinRemoveConditionIsAlwaysFalse() {
    final String sql = "SELECT e.deptno\n"
        + "FROM sales.dept d\n"
        + "RIGHT JOIN sales.emp e ON e.deptno = d.deptno and e.deptno between 3 and 1";
    sql(sql).withRule(CoreRules.JOIN_CONDITION_PUSH,
            PruneEmptyRules.JOIN_LEFT_INSTANCE)
        .check();
  }

  @Test void testSwapOuterJoin() {
    final HepProgram program = new HepProgramBuilder()
        .addMatchLimit(1)
        .addRuleInstance(CoreRules.JOIN_COMMUTE_OUTER)
        .build();
    final String sql = "select 1 from sales.dept d left outer join sales.emp e\n"
        + " on d.deptno = e.deptno";
    sql(sql).withProgram(program).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7089">[CALCITE-7089]
   * Implement a rule for converting a RIGHT JOIN to a LEFT JOIN</a>. */
  @Test void testSwapRightToLeftOnly() {
    final HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.JOIN_COMMUTE_RIGHT_TO_LEFT)
        .build();
    final String sql = "select 1 from sales.dept d right outer join sales.emp e\n"
        + " on d.deptno = e.deptno";
    sql(sql).withProgram(program).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4042">[CALCITE-4042]
   * JoinCommuteRule must not match SEMI / ANTI join</a>. */
  @Test void testSwapSemiJoin() {
    checkSwapJoinShouldNotMatch(JoinRelType.SEMI);
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4042">[CALCITE-4042]
   * JoinCommuteRule must not match SEMI / ANTI join</a>. */
  @Test void testSwapAntiJoin() {
    checkSwapJoinShouldNotMatch(JoinRelType.ANTI);
  }

  private void checkSwapJoinShouldNotMatch(JoinRelType type) {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .scan("DEPT")
        .join(type,
            b.equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO")))
        .project(b.field("EMPNO"))
        .build();
    relFn(relFn).withRule(CoreRules.JOIN_COMMUTE_OUTER).checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4621">[CALCITE-4621]
   * SemiJoinRule throws AssertionError on ANTI join</a>. */
  @Test void testJoinToSemiJoinRuleOnAntiJoin() {
    checkSemiJoinRuleOnAntiJoin(CoreRules.JOIN_TO_SEMI_JOIN);
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4621">[CALCITE-4621]
   * SemiJoinRule throws AssertionError on ANTI join</a>. */
  @Test void testProjectToSemiJoinRuleOnAntiJoin() {
    checkSemiJoinRuleOnAntiJoin(CoreRules.PROJECT_TO_SEMI_JOIN);
  }

  private void checkSemiJoinRuleOnAntiJoin(RelOptRule rule) {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("DEPT")
        .scan("EMP")
        .project(b.field("DEPTNO"))
        .distinct()
        .antiJoin(
            b.equals(
                b.field(2, 0, "DEPTNO"),
                b.field(2, 1, "DEPTNO")))
        .project(b.field("DNAME"))
        .build();
    relFn(relFn).withRule(rule).checkUnchanged();
  }

  @Test void testPushJoinCondDownToProject() {
    final String sql = "select d.deptno, e.deptno from sales.dept d, sales.emp e\n"
        + " where d.deptno + 10 = e.deptno * 2";
    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN,
            CoreRules.JOIN_PUSH_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4616">[CALCITE-4616]
   * AggregateUnionTransposeRule causes row type mismatch when some inputs have
   * unique grouping key</a>. */
  @Test void testAggregateUnionTransposeWithOneInputUnique() {
    final String sql = "select deptno, SUM(t) from (\n"
        + "select deptno, 1 as t from sales.emp e1\n"
        + "union all\n"
        + "select distinct deptno, 2 as t from sales.emp e2)\n"
        + "group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_UNION_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6642">[CALCITE-6642]
   * AggregateUnionTransposeRule throws an assertion error when creating children aggregates
   * when one input only has a non-nullable column</a>. */
  @Test void testAggregateUnionTransposeWithOneInputNonNullable() {
    final String sql = "select deptno, SUM(t) from (\n"
        + "select deptno, 1 as t from sales.emp e1\n"
        + "union all\n"
        + "select deptno, nullif(sal, 0) as t from sales.emp e2)\n"
        + "group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_UNION_TRANSPOSE)
        .check();
  }

  /** If all inputs to UNION are already unique, AggregateUnionTransposeRule is
   * a no-op. */
  @Test void testAggregateUnionTransposeWithAllInputsUnique() {
    final String sql = "select deptno, SUM(t) from (\n"
        + "select distinct deptno, 1 as t from sales.emp e1\n"
        + "union all\n"
        + "select distinct deptno, 2 as t from sales.emp e2)\n"
        + "group by deptno";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_UNION_TRANSPOSE)
        .checkUnchanged();
  }

  @Test void testAggregateUnionTransposeWithTopLevelGroupSetRemapping() {
    final String sql = "select count(t1), t2 from (\n"
        + "select (case when deptno=0 then 1 else null end) as t1, 1 as t2 from sales.emp e1\n"
        + "union all\n"
        + "select (case when deptno=0 then 1 else null end) as t1, 2 as t2 from sales.emp e2)\n"
        + "group by t2";
    sql(sql)
        .withPreRule(CoreRules.AGGREGATE_PROJECT_MERGE)
        .withRule(CoreRules.AGGREGATE_UNION_TRANSPOSE)
        .check();
  }

  @Test void testSortJoinTranspose1() {
    final String sql = "select * from sales.emp e left join (\n"
        + "  select * from sales.dept d) d on e.deptno = d.deptno\n"
        + "order by sal limit 10";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .check();
  }

  @Test void testSortJoinTranspose2() {
    final String sql = "select * from sales.emp e right join (\n"
        + "  select * from sales.dept d) d on e.deptno = d.deptno\n"
        + "order by name";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .check();
  }

  @Test void testSortJoinTranspose3() {
    // This one cannot be pushed down
    final String sql = "select * from sales.emp e left join (\n"
        + "  select * from sales.dept) d on e.deptno = d.deptno\n"
        + "order by sal, name limit 10";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-931">[CALCITE-931]
   * Wrong collation trait in SortJoinTransposeRule for right joins</a>. */
  @Test void testSortJoinTranspose4() {
    final String sql = "select * from sales.emp e right join (\n"
        + "  select * from sales.dept d) d on e.deptno = d.deptno\n"
        + "order by name";
    sql(sql).withFactory(t ->
        t.withPlannerFactory(context ->
            // Create a customized test with RelCollation trait in the test
            // cluster.
            new MockRelOptPlanner(Contexts.empty()) {
              @Override public List<RelTraitDef> getRelTraitDefs() {
                return ImmutableList.of(RelCollationTraitDef.INSTANCE);
              }
              @Override public RelTraitSet emptyTraitSet() {
                return RelTraitSet.createEmpty().plus(
                    RelCollationTraitDef.INSTANCE.getDefault());
              }
            }))
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1498">[CALCITE-1498]
   * Avoid LIMIT with trivial ORDER BY being pushed through JOIN endlessly</a>. */
  @Test void testSortJoinTranspose5() {
    // SortJoinTransposeRule should not be fired again.
    final String sql = "select * from sales.emp e right join (\n"
        + "  select * from sales.dept d) d on e.deptno = d.deptno\n"
        + "limit 10";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE,
            CoreRules.SORT_JOIN_TRANSPOSE,
            CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1507">[CALCITE-1507]
   * OFFSET cannot be pushed through a JOIN if the non-preserved side of outer
   * join is not count-preserving</a>. */
  @Test void testSortJoinTranspose6() {
    // This one can be pushed down even if it has an OFFSET, since the dept
    // table is count-preserving against the join condition.
    final String sql = "select d.deptno, empno from sales.dept d\n"
        + "right join sales.emp e using (deptno) limit 10 offset 2";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1507">[CALCITE-1507]
   * OFFSET cannot be pushed through a JOIN if the non-preserved side of outer
   * join is not count-preserving</a>. */
  @Test void testSortJoinTranspose7() {
    // This one cannot be pushed down
    final String sql = "select d.deptno, empno from sales.dept d\n"
        + "left join sales.emp e using (deptno) order by d.deptno offset 1";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6983">[CALCITE-6983]
   * SortJoinTransposeRule should not push SORT past a JOIN when SORT's fetch is DynamicParam
   * </a>. */
  @Test void testSortJoinTranspose8() {
    final String sql = "select * from sales.emp e right join (\n"
        + "  select * from sales.dept d) d on e.deptno = d.deptno\n"
        + "FETCH NEXT ? ROWS ONLY";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE,
            CoreRules.SORT_JOIN_TRANSPOSE,
            CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6983">[CALCITE-6983]
   * SortJoinTransposeRule should not push SORT past a JOIN when SORT's fetch is DynamicParam
   * </a>. */
  @Test void testSortJoinTranspose9() {
    final String sql = "select * from sales.emp e left join (\n"
        + "  select * from sales.dept d) d on e.deptno = d.deptno\n"
        + "OFFSET ? ROWS";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE,
            CoreRules.SORT_JOIN_TRANSPOSE,
            CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .checkUnchanged();
  }

  @Test void testSortJoinTranspose10() {
    final String sql = "select * from sales.emp e left join (\n"
        + "  select * from sales.dept d) d on e.deptno = d.deptno\n"
        + "limit 3000000000 offset 2500000000";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .check();
  }

  @Test void testSortProjectTranspose1() {
    // This one can be pushed down
    final String sql = "select d.deptno from sales.dept d\n"
        + "order by cast(d.deptno as integer) offset 1";
    sql(sql).withRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .check();
  }

  @Test void testSortProjectTranspose2() {
    // This one can be pushed down
    final String sql = "select d.deptno from sales.dept d\n"
        + "order by cast(d.deptno as double) offset 1";
    sql(sql).withRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .check();
  }

  @Test void testSortProjectTranspose3() {
    // This one cannot be pushed down
    final String sql = "select d.deptno from sales.dept d\n"
        + "order by cast(d.deptno as varchar(10)) offset 1";
    sql(sql).withRule(CoreRules.SORT_JOIN_TRANSPOSE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1023">[CALCITE-1023]
   * Planner rule that removes Aggregate keys that are constant</a>. */
  @Test void testAggregateConstantKeyRule() {
    final String sql = "select count(*) as c\n"
        + "from sales.emp\n"
        + "where deptno = 10\n"
        + "group by deptno, sal";
    sql(sql).withRule(CoreRules.AGGREGATE_ANY_PULL_UP_CONSTANTS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6586">[CALCITE-6586]
   * Some Rules not firing due to RelMdPredicates returning null in VolcanoPlanner</a>. */
  @Test void testAggregateConstantKeyRuleInVolcano() {
    final String sql = "select count(*) as c\n"
        + "from sales.emp\n"
        + "where deptno = 10\n"
        + "group by deptno, sal";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRule(CoreRules.AGGREGATE_ANY_PULL_UP_CONSTANTS);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_AGGREGATE_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_FILTER_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
        })
        .check();
  }

  /** Tests {@link AggregateProjectPullUpConstantsRule} where reduction is not
   * possible because "deptno" is the only key. */
  @Test void testAggregateConstantKeyRule2() {
    final String sql = "select count(*) as c\n"
        + "from sales.emp\n"
        + "where deptno = 10\n"
        + "group by deptno";
    sql(sql).withRule(CoreRules.AGGREGATE_ANY_PULL_UP_CONSTANTS)
        .checkUnchanged();
  }

  /** Tests {@link AggregateProjectPullUpConstantsRule} where both keys are
   * constants but only one can be removed. */
  @Test void testAggregateConstantKeyRule3() {
    final String sql = "select job\n"
        + "from sales.emp\n"
        + "where mgr is null and job = 'Clerk'\n"
        + "group by mgr, job\n"
        + "having count(*) > 3";
    sql(sql).withRule(CoreRules.AGGREGATE_ANY_PULL_UP_CONSTANTS)
        .check();
  }

  /** Tests {@link AggregateProjectPullUpConstantsRule} where
   * there are group keys of type
   * {@link org.apache.calcite.sql.fun.SqlAbstractTimeFunction}
   * that can not be removed. */
  @Test void testAggregateDynamicFunction() {
    final String sql = "select hiredate\n"
        + "from sales.emp\n"
        + "where mgr is null and hiredate = current_timestamp\n"
        + "group by mgr, hiredate\n"
        + "having count(*) > 3";
    sql(sql).withRule(CoreRules.AGGREGATE_ANY_PULL_UP_CONSTANTS)
        .check();
  }

  @Test void testReduceExpressionsNot() {
    final String sql = "select * from (values (false),(true)) as q (col1) where not(col1)";
    sql(sql).withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  /** Tests expanding a sub-query, specifically an uncorrelated scalar
   * sub-query in a project (SELECT clause). */
  @Test void testExpandProjectScalar() {
    final String sql = "select empno,\n"
        + "  (select deptno from sales.emp where empno < 20) as d\n"
        + "from sales.emp";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testSelectNotInCorrelated() {
    final String sql = "select sal,\n"
        + " empno NOT IN (\n"
        + " select deptno from dept\n"
        + "   where emp.job=dept.name)\n"
        + " from emp";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1493">[CALCITE-1493]
   * Wrong plan for NOT IN correlated queries</a>. */
  @Test void testWhereNotInCorrelated() {
    final String sql = "select sal from emp\n"
        + "where empno NOT IN (\n"
        + "  select deptno from dept\n"
        + "  where emp.job = dept.name)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testWhereNotInCorrelated2() {
    final String sql = "select * from emp e1\n"
        + "  where e1.empno NOT IN\n"
        + "   (select empno from (select ename, empno, sal as r from emp) e2\n"
        + "    where r > 2 and e1.ename= e2.ename)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testAll() {
    final String sql = "select * from emp e1\n"
        + "  where e1.empno > ALL (select deptno from dept)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testSome() {
    final String sql = "select * from emp e1\n"
        + "  where e1.empno > SOME (select deptno from dept)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for testing type created by SubQueryRemoveRule: an
   * ANY sub-query is non-nullable therefore plan should have cast. */
  @Test void testAnyInProjectNonNullable() {
    final String sql = "select name, deptno > ANY (\n"
        + "  select deptno from emp)\n"
        + "from dept";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for testing type created by SubQueryRemoveRule; an
   * ANY sub-query is nullable therefore plan should not have cast. */
  @Test void testAnyInProjectNullable() {
    final String sql = "select deptno, name = ANY (\n"
        + "  select mgr from emp)\n"
        + "from dept";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testSelectAnyCorrelated() {
    final String sql = "select empno > ANY (\n"
        + "  select deptno from dept where emp.job = dept.name)\n"
        + "from emp\n";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testWhereAnyCorrelatedInSelect() {
    final String sql = "select * from emp where empno > ANY (\n"
        + "  select deptno from dept where emp.job = dept.name)\n";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testSomeWithEquality() {
    final String sql = "select * from emp e1\n"
        + "  where e1.deptno = SOME (select deptno from dept)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testSomeWithEquality2() {
    final String sql = "select * from emp e1\n"
        + "  where e1.ename= SOME (select name from dept)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testSomeWithNotEquality() {
    final String sql = "select * from emp e1\n"
        + "  where e1.deptno <> SOME (select deptno from dept)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5655">[CALCITE-5655]
   * Wrong field reference lookup due to same intermediate table alias
   * of multiple sub-queries in subquery remove phase</a>. */
  @Test public void testSomeWithTwoCorrelatedSubQueries() {
    final String sql = "select empno from sales.empnullables as e\n"
        + "where deptno > some(\n"
        + "  select deptno from sales.deptnullables where e.ename = name and deptno > 10)\n"
        + "or deptno < some(\n"
        + "  select deptno from sales.deptnullables where e.ename = name and deptno < 20)";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .withTrim(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5655">[CALCITE-5655]
   * Wrong field reference lookup due to same intermediate table alias
   * of multiple sub-queries in subquery remove phase</a>. */
  @Test public void testSomeWithTwoSubQueries() {
    final String sql = "select empno from sales.empnullables\n"
        + "where deptno > some(\n"
        + "  select deptno from sales.deptnullables where name = 'dept1')\n"
        + "or deptno < some(\n"
        + "  select deptno from sales.deptnullables where name = 'dept2')";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .withTrim(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1546">[CALCITE-1546]
   * Sub-queries connected by OR</a>. */
  @Test void testWhereOrSubQuery() {
    final String sql = "select * from emp\n"
        + "where sal = 4\n"
        + "or empno NOT IN (select deptno from dept)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testExpandProjectIn() {
    final String sql = "select empno,\n"
        + "  deptno in (select deptno from sales.emp where empno < 20) as d\n"
        + "from sales.emp";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .check();
  }

  @Test void testExpandProjectInNullable() {
    final String sql = "with e2 as (\n"
        + "  select empno, case when deptno > 0 then deptno else null end as deptno\n"
        + "  from sales.emp)\n"
        + "select empno,\n"
        + "  deptno in (select deptno from e2 where empno < 20) as d\n"
        + "from e2";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .check();
  }

  @Test void testExpandProjectInWithTwoCorrelatedSubQueries() {
    final String sql = "select empno, deptno in (\n"
        + "  select deptno from sales.deptnullables where e.ename = name and deptno > 10)\n"
        + "or deptno in (\n"
        + "  select deptno from sales.deptnullables where e.ename = name and deptno < 20)\n"
        + "from sales.empnullables as e";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .withTrim(true)
        .check();
  }

  @Test void testExpandProjectInWithTwoSubQueries() {
    final String sql = "select empno, deptno in (\n"
        + "  select deptno from sales.deptnullables where name = 'dept1')\n"
        + "or deptno in (\n"
        + "  select deptno from sales.deptnullables where name = 'dept2')\n"
        + "from sales.empnullables";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .withTrim(true)
        .check();
  }

  @Test void testExpandProjectInComposite() {
    final String sql = "select empno, (empno, deptno) in (\n"
        + "    select empno, deptno from sales.emp where empno < 20) as d\n"
        + "from sales.emp";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .check();
  }

  @Test void testExpandProjectExists() {
    final String sql = "select empno,\n"
        + "  exists (select deptno from sales.emp where empno < 20) as d\n"
        + "from sales.emp";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .check();
  }

  @Test void testExpandFilterScalar() {
    final String sql = "select empno\n"
        + "from sales.emp\n"
        + "where (select deptno from sales.emp where empno < 20)\n"
        + " < (select deptno from sales.emp where empno > 100)\n"
        + "or emp.sal < 100";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testExpandFilterIn() {
    final String sql = "select empno\n"
        + "from sales.emp\n"
        + "where deptno in (select deptno from sales.emp where empno < 20)\n"
        + "or emp.sal < 100";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testExpandFilterInComposite() {
    final String sql = "select empno\n"
        + "from sales.emp\n"
        + "where (empno, deptno) in (\n"
        + "  select empno, deptno from sales.emp where empno < 20)\n"
        + "or emp.sal < 100";
    sql(sql).withSubQueryRules().check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5655">[CALCITE-5655]
   * Wrong field reference lookup due to same intermediate table alias
   * of multiple sub-queries in subquery remove phase</a>. */
  @Test void testExpandFilterInCorrelatedWithTwoSubQueries() {
    final String sql = "select empno from sales.empnullables as e\n"
        + "where deptno in (\n"
        + "  select deptno from sales.deptnullables where e.ename = name and deptno > 10)\n"
        + "or deptno in (\n"
        + "  select deptno from sales.deptnullables where e.ename = name and deptno < 20)";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .withTrim(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5655">[CALCITE-5655]
   * Wrong field reference lookup due to same intermediate table alias
   * of multiple sub-queries in subquery remove phase</a>. */
  @Test void testExpandFilterInWithTwoSubQueries() {
    final String sql = "select empno from sales.empnullables\n"
        + "where deptno in (\n"
        + "  select deptno from sales.deptnullables where name = 'dept1')\n"
        + "or deptno in (\n"
        + "  select deptno from sales.deptnullables where name = 'dept2')";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .withTrim(true)
        .check();
  }

  /** An IN filter that requires full 3-value logic (true, false, unknown). */
  @Test void testExpandFilterIn3Value() {
    final String sql = "select empno\n"
        + "from sales.emp\n"
        + "where empno\n"
        + " < case deptno in (select case when deptno > 0 then deptno else null end\n"
        + "                   from sales.emp where empno < 20)\n"
        + "   when true then 10\n"
        + "   when false then 20\n"
        + "   else 30\n"
        + "   end";
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5680">[CALCITE-5680]
   * Wrong field reference lookup due to same intermediate table alias
   * of multiple sub-queries with only literal operands in subquery remove phase</a>. */
  @Test void testExpandFilterConstantInCorrelatedWithTwoSubQueries() {
    final String sql = "select empno from sales.empnullables as e\n"
        + "where 1 in (\n"
        + "  select deptno from sales.deptnullables where e.ename = name and deptno > 10)\n"
        + "or 2 in (\n"
        + "  select deptno from sales.deptnullables where e.ename = name and deptno < 20)";
    // We disable expression simplification and enable trim to make plan
    // more straightforward and easier to identify whether the plan is correct
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .withTrim(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5680">[CALCITE-5680]
   * Wrong field reference lookup due to same intermediate table alias
   * of multiple sub-queries with only literal operands in subquery remove phase</a>. */
  @Test void testExpandFilterConstantInWithTwoSubQueries() {
    final String sql = "select empno from sales.empnullables\n"
        + "where 1 in (\n"
        + "  select deptno from sales.deptnullables where name = 'dept1')\n"
        + "or 2 in (\n"
        + "  select deptno from sales.deptnullables where name = 'dept2')";
    // We disable expression simplification and enable trim to make plan
    // more straightforward and easier to identify whether the plan is correct
    sql(sql)
        .withSubQueryRules()
        .withRelBuilderSimplify(false)
        .withTrim(true)
        .check();
  }

  /** An EXISTS filter that can be converted into true/false. */
  @Test void testExpandFilterExists() {
    final String sql = "select empno\n"
        + "from sales.emp\n"
        + "where exists (select deptno from sales.emp where empno < 20)\n"
        + "or emp.sal < 100";
    sql(sql).withSubQueryRules().check();
  }

  /** An EXISTS filter that can be converted into a semi-join. */
  @Test void testExpandFilterExistsSimple() {
    final String sql = "select empno\n"
        + "from sales.emp\n"
        + "where exists (select deptno from sales.emp where empno < 20)";
    sql(sql).withSubQueryRules().check();
  }

  /** An EXISTS filter that can be converted into a semi-join. */
  @Test void testExpandFilterExistsSimpleAnd() {
    final String sql = "select empno\n"
        + "from sales.emp\n"
        + "where exists (select deptno from sales.emp where empno < 20)\n"
        + "and emp.sal < 100";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testExpandJoinScalar() {
    final String sql = "select empno\n"
        + "from sales.emp left join sales.dept\n"
        + "on (select deptno from sales.emp where empno < 20)\n"
        + " < (select deptno from sales.emp where empno > 100)";
    sql(sql).withSubQueryRules().check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3121">[CALCITE-3121]
   * VolcanoPlanner hangs due to sub-query with dynamic star</a>. */
  @Test void testSubQueryWithDynamicStarHang() {
    String sql = "select n.n_regionkey\n"
        + "from (select *\n"
        + "  from (select *\n"
        + "    from sales.customer) t) n\n"
        + "where n.n_nationkey > 1";

    PushProjector.ExprCondition exprCondition = expr -> {
      if (expr instanceof RexCall) {
        RexCall call = (RexCall) expr;
        return "item".equals(call.getOperator().getName().toLowerCase(Locale.ROOT));
      }
      return false;
    };
    RuleSet ruleSet =
        RuleSets.ofList(CoreRules.FILTER_PROJECT_TRANSPOSE,
            CoreRules.FILTER_MERGE,
            CoreRules.PROJECT_MERGE,
            ProjectFilterTransposeRule.Config.DEFAULT
                .withOperandFor(Project.class, Filter.class)
                .withPreserveExprCondition(exprCondition)
                .toRule(),
            EnumerableRules.ENUMERABLE_PROJECT_RULE,
            EnumerableRules.ENUMERABLE_FILTER_RULE,
            EnumerableRules.ENUMERABLE_SORT_RULE,
            EnumerableRules.ENUMERABLE_LIMIT_RULE,
            EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
    Program program = Programs.of(ruleSet);

    sql(sql)
        .withVolcanoPlanner(false)
        .withDynamicTable()
        .withDecorrelate(true)
        .withAfter((fixture, r) -> {
          RelTraitSet toTraits =
              r.getCluster().traitSet()
                  .replace(0, EnumerableConvention.INSTANCE);
          return program.run(fixture.planner, r, toTraits,
              ImmutableList.of(), ImmutableList.of());
        })
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3188">[CALCITE-3188]
   * IndexOutOfBoundsException in ProjectFilterTransposeRule when executing SELECT COUNT(*)</a>. */
  @Test void testProjectFilterTransposeRuleOnEmptyRowType() {
    // build a rel equivalent to sql:
    // select `empty` from emp
    // where emp.deptno = 20
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .filter(b
            .equals(
                b.field(1, 0, "DEPTNO"),
                b.literal(20)))
        .project(ImmutableList.of())
        .build();
    relFn(relFn).withRule(CoreRules.PROJECT_FILTER_TRANSPOSE).check();
  }

  @Test void testFlattenUncorrelatedCallBelowEquals() {
    final String sql = "select * from emp e1 where exists ("
        + "select * from emp e2 where e1.deptno = (e2.deptno+30))";
    sql(sql).withDecorrelate(false)
        .withExpand(true)
        .withRule(FilterFlattenCorrelatedConditionRule.Config.DEFAULT.toRule())
        .check();
  }

  @Test void testCallOverCorrelationVariableIsNotFlattened() {
    final String sql = "select * from emp e1 where exists ("
        + "select * from emp e2 where (e1.deptno+30) = e2.deptno)";
    sql(sql).withDecorrelate(false)
        .withExpand(true)
        .withRule(FilterFlattenCorrelatedConditionRule.Config.DEFAULT.toRule())
        .checkUnchanged();
  }

  @Test void testFlattenUncorrelatedTwoLevelCallBelowEqualsSucceeds() {
    final String sql = "select * from emp e1 where exists ("
        + "select * from emp e2 where e1.deptno = (2 * e2.deptno+30))";
    sql(sql).withDecorrelate(false)
        .withExpand(true)
        .withRule(FilterFlattenCorrelatedConditionRule.Config.DEFAULT.toRule())
        .check();
  }

  @Test void testUncorrelatedCallBelowNonComparisonOpIsNotFlattened() {
    final String sql = "select * from emp e1 where exists ("
        + "select * from emp e2 where (e1.deptno + (e2.deptno+30)) > 0)";
    sql(sql).withDecorrelate(false)
        .withExpand(true)
        .withRule(FilterFlattenCorrelatedConditionRule.Config.DEFAULT.toRule())
        .checkUnchanged();
  }

  @Test void testUncorrelatedCallInConjunctionIsFlattenedOnlyIfSiblingOfCorrelation() {
    final String sql = "select * from emp e1 where exists ("
        + "select * from emp e2 where (e2.empno+50) < 20 and e1.deptno >= (30+e2.deptno))";
    sql(sql).withDecorrelate(false)
        .withExpand(true)
        .withRule(FilterFlattenCorrelatedConditionRule.Config.DEFAULT.toRule())
        .check();
  }

  @Disabled("[CALCITE-1045]")
  @Test void testExpandJoinIn() {
    final String sql = "select empno\n"
        + "from sales.emp left join sales.dept\n"
        + "on emp.deptno in (select deptno from sales.emp where empno < 20)";
    sql(sql).withSubQueryRules().check();
  }

  @Disabled("[CALCITE-1045]")
  @Test void testExpandJoinInComposite() {
    final String sql = "select empno\n"
        + "from sales.emp left join sales.dept\n"
        + "on (emp.empno, dept.deptno) in (\n"
        + "  select empno, deptno from sales.emp where empno < 20)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testExpandJoinExists() {
    final String sql = "select empno\n"
        + "from sales.emp left join sales.dept\n"
        + "on exists (select deptno from sales.emp where empno < 20)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testDecorrelateExists() {
    final String sql = "select * from sales.emp\n"
        + "where EXISTS (\n"
        + "  select * from emp e where emp.deptno = e.deptno)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1511">[CALCITE-1511]
   * AssertionError while decorrelating query with two EXISTS
   * sub-queries</a>. */
  @Test void testDecorrelateTwoExists() {
    final String sql = "select * from sales.emp\n"
        + "where EXISTS (\n"
        + "  select * from emp e where emp.deptno = e.deptno)\n"
        + "AND NOT EXISTS (\n"
        + "  select * from emp ee where ee.job = emp.job AND ee.sal=34)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2028">[CALCITE-2028]
   * Un-correlated IN sub-query should be converted into a Join,
   * rather than a Correlate without correlation variables </a>. */
  @Test void testDecorrelateUncorrelatedInAndCorrelatedExists() {
    final String sql = "select * from sales.emp\n"
        + "WHERE job in (\n"
        + "  select job from emp ee where ee.sal=34)"
        + "AND EXISTS (\n"
        + "  select * from emp e where emp.deptno = e.deptno)\n";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1537">[CALCITE-1537]
   * Unnecessary project expression in multi-sub-query plan</a>. */
  @Test void testDecorrelateTwoIn() {
    final String sql = "select sal\n"
        + "from sales.emp\n"
        + "where empno IN (\n"
        + "  select deptno from dept where emp.job = dept.name)\n"
        + "AND empno IN (\n"
        + "  select empno from emp e where emp.ename = e.ename)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1045">[CALCITE-1045]
   * Decorrelate sub-queries in Project and Join</a>, with the added
   * complication that there are two sub-queries. */
  @Disabled("[CALCITE-1045]")
  @Test void testDecorrelateTwoScalar() {
    final String sql = "select deptno,\n"
        + "  (select min(1) from emp where empno > d.deptno) as i0,\n"
        + "  (select min(0) from emp\n"
        + "    where deptno = d.deptno and ename = 'SMITH') as i1\n"
        + "from dept as d";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testWhereInJoinCorrelated() {
    final String sql = "select empno from emp as e\n"
        + "join dept as d using (deptno)\n"
        + "where e.sal in (\n"
        + "  select e2.sal from emp as e2 where e2.deptno > e.deptno)";
    sql(sql).withSubQueryRules().check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-1494">[CALCITE-1494]
   * Inefficient plan for correlated sub-queries</a>. In "planAfter", there
   * must be only one scan each of emp and dept. We don't need a separate
   * value-generator for emp.job. */
  @Test void testWhereInCorrelated() {
    final String sql = "select sal from emp where empno IN (\n"
        + "  select deptno from dept where emp.job = dept.name)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testWhereExpressionInCorrelated() {
    final String sql = "select ename from (\n"
        + "  select ename, deptno, sal + 1 as salPlus from emp) as e\n"
        + "where deptno in (\n"
        + "  select deptno from emp where sal + 1 = e.salPlus)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testWhereExpressionInCorrelated2() {
    final String sql = "select name from (\n"
        + "  select name, deptno, deptno - 10 as deptnoMinus from dept) as d\n"
        + "where deptno in (\n"
        + "  select deptno from emp where sal + 1 = d.deptnoMinus)";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  @Test void testExpandWhereComparisonCorrelated() {
    final String sql = "select empno\n"
        + "from sales.emp as e\n"
        + "where sal = (\n"
        + "  select max(sal) from sales.emp e2 where e2.empno = e.empno)";
    sql(sql).withSubQueryRules().check();
  }

  @Test void testCustomColumnResolvingInNonCorrelatedSubQuery() {
    final String sql = "select *\n"
        + "from struct.t t1\n"
        + "where c0 in (\n"
        + "  select f1.c0 from struct.t t2)";
    sql(sql).withSubQueryRules().withTrim(true).check();
  }

  @Test void testCustomColumnResolvingInCorrelatedSubQuery() {
    final String sql = "select *\n"
        + "from struct.t t1\n"
        + "where c0 = (\n"
        + "  select max(f1.c0) from struct.t t2 where t1.k0 = t2.k0)";
    sql(sql).withSubQueryRules().withTrim(true).check();
  }

  @Test void testCustomColumnResolvingInCorrelatedSubQuery2() {
    final String sql = "select *\n"
        + "from struct.t t1\n"
        + "where c0 in (\n"
        + "  select f1.c0 from struct.t t2 where t1.c2 = t2.c2)";
    sql(sql).withSubQueryRules().withTrim(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2744">[CALCITE-2744]
   * RelDecorrelator use wrong output map for LogicalAggregate decorrelate</a>. */
  @Test void testDecorrelateAggWithConstantGroupKey() {
    final String sql = "SELECT *\n"
        + "FROM emp A\n"
        + "where sal in (SELECT max(sal)\n"
        + "  FROM emp B\n"
        + "  where A.mgr = B.empno\n"
        + "  group by deptno, 'abc')";
    sql(sql)
        .withExpand(true)
        .withLateDecorrelate(true)
        .withTrim(true)
        .withRule() // empty program
        .check();
  }

  /** Test case for CALCITE-2744 for aggregate decorrelate with multi-param agg call
   * but without group key. */
  @Test void testDecorrelateAggWithMultiParamsAggCall() {
    final String sql = "SELECT * FROM (SELECT MYAGG(sal, 1) AS c FROM emp) as m,\n"
        + " LATERAL TABLE(ramp(m.c)) AS T(s)";
    sql(sql)
        .withLateDecorrelate(true)
        .withTrim(true)
        .withRule() // empty program
        .checkUnchanged();
  }

  /** Same as {@link #testDecorrelateAggWithMultiParamsAggCall}
   * but with a constant group key. */
  @Test void testDecorrelateAggWithMultiParamsAggCall2() {
    final String sql = "SELECT * FROM "
        + "(SELECT MYAGG(sal, 1) AS c FROM emp group by empno, 'abc') as m,\n"
        + " LATERAL TABLE(ramp(m.c)) AS T(s)";
    sql(sql)
        .withLateDecorrelate(true)
        .withTrim(true)
        .withRule() // empty program
        .checkUnchanged();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6681">[CALCITE-6681]
   * NullPointerException in ProjectCorrelateTransposeRule</a>. */
  @Test void testLateralTransposeWithDecorrelateFalse() {
    final String sql = "WITH "
        + "  t1(a, ts) AS (VALUES('a', 1)),"
        + "  t2(a, ts, x) AS (SELECT ename as a, empno as ts, mgr as x FROM emp)\n"
        + "SELECT * FROM t1\n"
        + "LEFT JOIN LATERAL (\n"
        + "    SELECT x FROM t2\n"
        + "    WHERE t2.a = t1.a AND t2.ts <= t1.ts\n"
        + "    LIMIT 1\n"
        + ") ON true\n"
        + "LEFT JOIN LATERAL (\n"
        + "    SELECT x\n"
        + "    FROM t2\n"
        + "    WHERE t2.a = t1.a\n"
        + ") ON true";
    sql(sql).withDecorrelate(false)
        .withRule(CoreRules.PROJECT_CORRELATE_TRANSPOSE)
        .checkUnchanged();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6681">[CALCITE-6681]
   * NullPointerException in ProjectCorrelateTransposeRule</a>. */
  @Test void testLateralTransposeWithDecorrelateTrue() {
    final String sql = "WITH "
        + "  t1(a, ts) AS (VALUES('a', 1)),"
        + "  t2(a, ts, x) AS (SELECT ename as a, empno as ts, mgr as x FROM emp)\n"
        + "SELECT * FROM t1\n"
        + "LEFT JOIN LATERAL (\n"
        + "    SELECT x FROM t2\n"
        + "    WHERE t2.a = t1.a AND t2.ts <= t1.ts\n"
        + "    LIMIT 1\n"
        + ") ON true\n"
        + "LEFT JOIN LATERAL (\n"
        + "    SELECT x\n"
        + "    FROM t2\n"
        + "    WHERE t2.a = t1.a\n"
        + ") ON true";
    sql(sql).withDecorrelate(true)
        .withRule(CoreRules.PROJECT_CORRELATE_TRANSPOSE)
        .checkUnchanged();
  }

  /** Test case for CALCITE-5683 for two level nested decorrelate with standard program
   * failing during the decorrelation phase. The correlation variable is used at two levels
   * deep. */
  @Test void testTwoLevelDecorrelate() {
    final String sql = "SELECT d1.name, d1.deptno + (\n"
        + "SELECT e1.empno\n"
        + "FROM emp e1\n"
        + "WHERE d1.deptno = e1.deptno and\n"
        + "    e1.sal = (SELECT max(sal)\n"
        + "              FROM emp e2\n"
        + "              WHERE e1.sal = e2.sal and\n"
        + "                  e1.deptno = e2.deptno and\n"
        + "                  d1.deptno < e2.deptno))\n"
        + "FROM dept d1";

    sql(sql)
        .withSubQueryRules()
        .withLateDecorrelate(true)
        .withTrim(true)
        .check();
  }

  @Test void testCorrelatedFilterWithVariable() {
    // select *
    // from dept
    // where exists (select deptno
    //               from emp
    //               where dept.deptno = emp.deptno
    //                 and emp.sal > 100)
    final Holder<@Nullable RexCorrelVariable> v = Holder.empty();
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("DEPT")
        .variable(v::set)
        .filter(
            ImmutableSet.of(v.get().id),
            b.exists(b1 -> b1
            .scan("EMP")
            .filter(
                b1.and(
                b1.equals(b1.field(v.get(), "DEPTNO"), b1.field("DEPTNO")),
                b1.greaterThan(b1.field("SAL"), b1.literal(100))))
            .project(b1.field("DEPTNO"))
            .build()))
        .build();
    relFn(relFn)
        .withSubQueryRules()
        .check();
  }

  /** Test case for CALCITE-5683 for two level nested decorrelate with standard program
   * failing during the decorrelation phase. The correlation variable is used at the second
   * level and is not used in the first level */
  @Test void testCorrelatedVariableAtSecondLevel() {
    final String sql = "SELECT d1.name, d1.deptno +(\n"
        + "SELECT e1.empno\n"
        + "FROM emp e1\n"
        + "WHERE e1.sal = (SELECT max(sal)\n"
        + "                FROM emp e2\n"
        + "                WHERE e1.sal = e2.sal and\n"
        + "                    e1.deptno = e2.deptno and\n"
        + "                    d1.deptno < e2.deptno))\n"
        + "FROM dept d1";
    sql(sql)
        .withSubQueryRules()
        .withLateDecorrelate(true)
        .withTrim(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6652">[CALCITE-6652]
   * RelDecorrelator can't decorrelate query with limit 1</a>.
   */
  @Test void testDecorrelateProjectWithFetchOne() {
    final String query = "SELECT name, "
        + "(SELECT sal FROM emp where dept.deptno = emp.deptno order by sal limit 1) "
        + "FROM dept";
    sql(query).withRule(CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE)
        .withLateDecorrelate(true)
        .check();
  }

  /** Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-7289">[CALCITE-7289]
   * Select NULL subquery throwing exception</a>. */
  @Test void testNullSelect() {
    final String sql = "SELECT 1 from emp WHERE NULL IN (SELECT null)";
    RelBuilder.Config config =
        RelBuilder.Config.DEFAULT.withSimplifyValues(false).withSimplify(false);
    RelOptRule subQueryFilterRule =
        SubQueryRemoveRule.Config.FILTER.withRelBuilderFactory(RelBuilder.proto(config)).toRule();
    sql(sql).withRule(subQueryFilterRule).withLateDecorrelate(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6652">[CALCITE-6652]
   * RelDecorrelator can't decorrelate query with limit 1</a>.
   */
  @Test void testDecorrelateProjectWithFetchOneDesc() {
    final String query = "SELECT name, "
        + "(SELECT emp.sal FROM emp WHERE dept.deptno = emp.deptno "
        + "ORDER BY emp.sal desc nulls last LIMIT 1) "
        + "FROM dept";
    sql(query).withRule(CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE)
        .withLateDecorrelate(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6652">[CALCITE-6652]
   * RelDecorrelator can't decorrelate query with limit 1</a>.
   */
  @Test void testDecorrelateFilterWithFetchOne() {
    final String query = "SELECT name FROM dept "
        + "WHERE 10 > (SELECT emp.sal FROM emp where dept.deptno = emp.deptno "
        + "ORDER BY emp.sal limit 1)";
    sql(query).withRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withLateDecorrelate(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6652">[CALCITE-6652]
   * RelDecorrelator can't decorrelate query with limit 1</a>.
   */
  @Test void testDecorrelateFilterWithFetchOneDesc() {
    final String query = "SELECT name FROM dept "
        + "WHERE 10 > (SELECT emp.sal FROM emp where dept.deptno = emp.deptno "
        + "ORDER BY emp.sal desc nulls last limit 1)";
    sql(query).withRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withLateDecorrelate(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6652">[CALCITE-6652]
   * RelDecorrelator can't decorrelate query with limit 1</a>.
   */
  @Test void testDecorrelateFilterWithFetchOneDesc1() {
    final String query = "SELECT name FROM dept "
        + "WHERE 10 > (SELECT emp.sal FROM emp where dept.deptno = emp.deptno "
        + "ORDER BY emp.sal desc limit 1)";
    sql(query).withRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withLateDecorrelate(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6652">[CALCITE-6652]
   * RelDecorrelator can't decorrelate query with limit 1</a>.
   */
  @Test void testDecorrelateProjectWithMultiKeyAndFetchOne() {
    final String query = "SELECT name, "
        + "(SELECT sal FROM emp where dept.deptno = emp.deptno "
        + "order by year(hiredate), emp.sal limit 1) FROM dept";
    sql(query).withRule(CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE)
        .withLateDecorrelate(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6652">[CALCITE-6652]
   * RelDecorrelator can't decorrelate query with limit 1</a>.
   */
  @Test void testDecorrelateProjectWithMultiKeyAndFetchOne1() {
    final String query = "SELECT name, "
        + "(SELECT sal FROM emp where dept.deptno = emp.deptno and dept.name = emp.ename "
        + "order by year(hiredate), emp.sal limit 1) FROM dept";
    sql(query).withRule(CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE)
        .withLateDecorrelate(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6652">[CALCITE-6652]
   * RelDecorrelator can't decorrelate query with limit 1</a>.
   */
  @Test void testDecorrelateFilterWithMultiKeyAndFetchOne() {
    final String query = "SELECT name FROM dept "
        + "WHERE 10 > (SELECT emp.sal FROM emp where dept.deptno = emp.deptno "
        + "order by year(hiredate), emp.sal desc limit 1)";
    sql(query).withRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE)
        .withLateDecorrelate(true)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-434">[CALCITE-434]
   * Converting predicates on date dimension columns into date ranges</a>,
   * specifically a rule that converts {@code EXTRACT(YEAR FROM ...) = constant}
   * to a range. */
  @Test void testExtractYearToRange() {
    final String sql = "select *\n"
        + "from sales.emp_b as e\n"
        + "where extract(year from birthdate) = 2014";
    sql(sql).withRule(DateRangeRules.FILTER_INSTANCE)
        .withContext(c -> Contexts.of(CalciteConnectionConfig.DEFAULT, c))
        .check();
  }

  @Test void testExtractYearMonthToRange() {
    final String sql = "select *\n"
        + "from sales.emp_b as e\n"
        + "where extract(year from birthdate) = 2014"
        + "and extract(month from birthdate) = 4";
    sql(sql).withRule(DateRangeRules.FILTER_INSTANCE)
        .withContext(c -> Contexts.of(CalciteConnectionConfig.DEFAULT, c))
        .check();
  }

  @Test void testFilterRemoveIsNotDistinctFromRule() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .filter(
            b.call(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM,
            b.field("DEPTNO"), b.literal(20)))
        .build();
    relFn(relFn).withRule(CoreRules.FILTER_EXPAND_IS_NOT_DISTINCT_FROM).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6927">[CALCITE-6927]
   * Add rule for join condition remove IS NOT DISTINCT FROM</a>. */
  @Test void testJoinConditinExpandIsNotDistinctFromRule() {
    final String sql = ""
        + "select t1.ENAME from empnullables as t1\n"
        + "join\n"
        + "empnullables as t2\n"
        + "on t1.ENAME is not distinct from t2.ENAME";
    sql(sql)
        .withRule(CoreRules.JOIN_CONDITION_EXPAND_IS_NOT_DISTINCT_FROM)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6927">[CALCITE-6927]
   * Add rule for join condition remove IS NOT DISTINCT FROM</a>. */
  @Test void testJoinConditinExpandIsNotDistinctFromRuleArray() {
    final String sql = ""
        + "select t1.ADMINS from DEPT_NESTED_EXPANDED as t1\n"
        + "join\n"
        + "DEPT_NESTED_EXPANDED as t2\n"
        + "on t1.ADMINS is not distinct from t2.ADMINS";
    sql(sql)
        .withRule(CoreRules.JOIN_CONDITION_EXPAND_IS_NOT_DISTINCT_FROM)
        .check();
  }

  /** Creates an environment for testing spatial queries. */
  private RelOptFixture spatial(String sql) {
    final HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.PROJECT_REDUCE_EXPRESSIONS)
        .addRuleInstance(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .addRuleInstance(SpatialRules.INSTANCE)
        .build();
    return sql(sql)
        .withCatalogReaderFactory(MockCatalogReaderExtended::create)
        .withConformance(SqlConformanceEnum.LENIENT)
        .withProgram(program);
  }

  /** Tests that a call to {@code ST_DWithin}
   * is rewritten with an additional range predicate. */
  @Test void testSpatialDWithinToHilbert() {
    final String sql = "select *\n"
        + "from GEO.Restaurants as r\n"
        + "where ST_DWithin(ST_Point(10.0, 20.0),\n"
        + "                 ST_Point(r.longitude, r.latitude), 10)";
    spatial(sql).check();
  }

  /** Tests that a call to {@code ST_DWithin}
   * is rewritten with an additional range predicate. */
  @Test void testSpatialDWithinToHilbertZero() {
    final String sql = "select *\n"
        + "from GEO.Restaurants as r\n"
        + "where ST_DWithin(ST_Point(10.0, 20.0),\n"
        + "                 ST_Point(r.longitude, r.latitude), 0)";
    spatial(sql).check();
  }

  @Test void testSpatialDWithinToHilbertNegative() {
    final String sql = "select *\n"
        + "from GEO.Restaurants as r\n"
        + "where ST_DWithin(ST_Point(10.0, 20.0),\n"
        + "                 ST_Point(r.longitude, r.latitude), -2)";
    spatial(sql).check();
  }

  /** As {@link #testSpatialDWithinToHilbert()} but arguments reversed. */
  @Test void testSpatialDWithinReversed() {
    final String sql = "select *\n"
        + "from GEO.Restaurants as r\n"
        + "where ST_DWithin(ST_Point(r.longitude, r.latitude),\n"
        + "                 ST_Point(10.0, 20.0), 6)";
    spatial(sql).check();
  }

  /** Points within a given distance of a line. */
  @Test void testSpatialDWithinLine() {
    final String sql = "select *\n"
        + "from GEO.Restaurants as r\n"
        + "where ST_DWithin(\n"
        + "  ST_MakeLine(ST_Point(8.0, 20.0), ST_Point(12.0, 20.0)),\n"
        + "  ST_Point(r.longitude, r.latitude), 4)";
    spatial(sql).check();
  }

  /** Points near a constant point, using ST_Contains and ST_Buffer. */
  @Test void testSpatialContainsPoint() {
    final String sql = "select *\n"
        + "from GEO.Restaurants as r\n"
        + "where ST_Contains(\n"
        + "  ST_Buffer(ST_Point(10.0, 20.0), 6),\n"
        + "  ST_Point(r.longitude, r.latitude))";
    spatial(sql).check();
  }

  /** Constant reduction on geo-spatial expression. */
  @Test void testSpatialReduce() {
    final String sql = "select\n"
        + "  ST_Buffer(ST_Point(0.0, 0.0), 1, 4) as b\n"
        + "from GEO.Restaurants as r";
    spatial(sql)
        .withRelBuilderSimplify(false)
        .check();
  }

  @Test void testOversimplifiedCaseStatement() {
    String sql = "select * from emp "
        + "where MGR > 0 and "
        + "case when MGR > 0 then deptno / MGR else null end > 1";
    sql(sql).withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
  * <a href="https://issues.apache.org/jira/browse/CALCITE-2726">[CALCITE-2726]
  * ReduceExpressionRule may oversimplify filter conditions containing nulls</a>.
  */
  @Test void testNoOversimplificationBelowIsNull() {
    String sql = "select *\n"
        + "from emp\n"
        + "where ( (empno=1 and mgr=1) or (empno=null and mgr=1) ) is null";
    sql(sql).withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6930">[CALCITE-6930]
   * Implementing JoinConditionOrExpansionRule</a>. */
  @Test void testJoinConditionOrExpansionRule() {
    String sql = "select *\n"
        + "from EMP as p1\n"
        + "inner join EMP as p2 on p1.empno = p2.empno or p1.mgr = p2.mgr";
    sql(sql).withRule(CoreRules.JOIN_EXPAND_OR_TO_UNION_RULE)
        .check();
  }

  @Test void testJoinConditionOrExpansionRule2() {
    String sql = "select * from empnullables as t1 inner join empnullables as t2\n"
        + "on (t1.empno = t2.empno\n"
        + "and t1.job = 'Job1'\n"
        + "and t1.ename in ('a', 'bb', 'cc')\n"
        + "and t1.sal > 120 and t1.sal < 3000\n"
        + "and t1.mgr = t1.comm)\n"
        + "or\n"
        + "(t1.deptno = t2.deptno\n"
        + "and t2.job = 'Job2'\n"
        + "and t2.ename in ('a', 'bb', 'cc')\n"
        + "and t2.sal > 110 and t2.sal < 3000\n"
        + "and t1.mgr + 10 < ln(15))\n"
        + "or\n"
        + "(t1.ename = 'Jensen'\n"
        + "and t2.comm > 10)\n"
        + "or\n"
        + "t1.mgr between 10.0 and 20\n";

    sql(sql).withRule(CoreRules.JOIN_EXPAND_OR_TO_UNION_RULE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6930">[CALCITE-6930]
   * Implementing JoinConditionOrExpansionRule</a>. */
  @Test void testJoinConditionOrExpansionRuleMultiOr() {
    String sql = "select *\n"
        + "from EMP as p1\n"
        + "inner join EMP as p2 on  p1.mgr < p2.mgr or\n"
        + "p1.empno = p2.empno or p1.sal < 0 or ln(p1.sal) < 10";
    sql(sql).withRule(CoreRules.JOIN_EXPAND_OR_TO_UNION_RULE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6930">[CALCITE-6930]
   * Implementing JoinConditionOrExpansionRule</a>. */
  @Test void testJoinConditionOrExpansionRuleRight() {
    String sql = "select *\n"
        + "from EMP as p1\n"
        + "right join DEPT as p2 on p1.empno = p2.deptno or p1.ename < p2.name";
    sql(sql).withRule(CoreRules.JOIN_EXPAND_OR_TO_UNION_RULE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6930">[CALCITE-6930]
   * Implementing JoinConditionOrExpansionRule</a>. */
  @Test void testJoinConditionOrExpansionRuleLeft() {
    String sql = "select *\n"
        + "from EMP as p1\n"
        + "left join EMP as p2 on p1.empno = p2.empno or p1.sal = p2.sal";
    sql(sql).withRule(CoreRules.JOIN_EXPAND_OR_TO_UNION_RULE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6930">[CALCITE-6930]
   * Implementing JoinConditionOrExpansionRule</a>. */
  @Test void testJoinConditionOrExpansionRuleAnti() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("DEPT")
        .scan("EMP")
        .antiJoin(
            b.or(
              b.equals(
                  b.field(2, 0, "DEPTNO"),
                  b.field(2, 1, "DEPTNO")),
              b.equals(
                  b.field(2, 0, "DNAME"),
                  b.field(2, 1, "JOB"))))
        .build();
    relFn(relFn)
        .withRule(CoreRules.JOIN_EXPAND_OR_TO_UNION_RULE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6930">[CALCITE-6930]
   * Implementing JoinConditionOrExpansionRule</a>. */
  @Test void testJoinConditionOrExpansionRuleFull() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("DEPT")
        .scan("EMP")
        .join(JoinRelType.FULL,
            b.or(
                b.equals(
                    b.field(2, 0, "DEPTNO"),
                    b.field(2, 1, "DEPTNO")),
                b.equals(
                    b.field(2, 0, "DNAME"),
                    b.field(2, 1, "JOB"))))
        .build();
    relFn(relFn)
        .withRule(CoreRules.JOIN_EXPAND_OR_TO_UNION_RULE)
        .check();
  }

  @Test void testExchangeRemoveConstantKeysRule() {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .filter(
            b.call(
                SqlStdOperatorTable.EQUALS,
                b.field("EMPNO"),
                b.literal(10)))
        .exchange(RelDistributions.hash(ImmutableList.of(0)))
        .project(
            b.field(0),
            b.field(1))
        .sortExchange(
            RelDistributions.hash(ImmutableList.of(0, 1)),
            RelCollations.of(new RelFieldCollation(0), new RelFieldCollation(1)))
        .build();

    relFn(relFn)
        .withRule(
            CoreRules.EXCHANGE_REMOVE_CONSTANT_KEYS,
            CoreRules.SORT_EXCHANGE_REMOVE_CONSTANT_KEYS)
        .check();
  }

  @Test void testReduceAverageWithNoReduceSum() {
    final RelOptRule rule = AggregateReduceFunctionsRule.Config.DEFAULT
        .withOperandFor(LogicalAggregate.class)
        .withFunctionsToReduce(EnumSet.of(SqlKind.AVG))
        .toRule();
    final String sql = "select name, max(name), avg(deptno), min(name)\n"
        + "from sales.dept group by name";
    sql(sql).withRule(rule).check();
  }

  @Test void testNoReduceAverage() {
    final RelOptRule rule = AggregateReduceFunctionsRule.Config.DEFAULT
        .withOperandFor(LogicalAggregate.class)
        .withFunctionsToReduce(EnumSet.noneOf(SqlKind.class))
        .toRule();
    String sql = "select name, max(name), avg(deptno), min(name)"
        + " from sales.dept group by name";
    sql(sql).withRule(rule).checkUnchanged();
  }

  @Test void testNoReduceSum() {
    final RelOptRule rule = AggregateReduceFunctionsRule.Config.DEFAULT
        .withOperandFor(LogicalAggregate.class)
        .withFunctionsToReduce(EnumSet.noneOf(SqlKind.class))
        .toRule();
    String sql = "select name, sum(deptno)"
            + " from sales.dept group by name";
    sql(sql).withRule(rule).checkUnchanged();
  }

  @Test void testReduceAverageAndVarWithNoReduceStddev() {
    // configure rule to reduce AVG and VAR_POP functions
    // other functions like SUM, STDDEV won't be reduced
    final RelOptRule rule = AggregateReduceFunctionsRule.Config.DEFAULT
        .withOperandFor(LogicalAggregate.class)
        .withFunctionsToReduce(EnumSet.of(SqlKind.AVG, SqlKind.VAR_POP))
        .toRule();
    final String sql = "select name, stddev_pop(deptno), avg(deptno),"
        + " var_pop(deptno)\n"
        + "from sales.dept group by name";
    sql(sql).withRule(rule).check();
  }

  @Test void testReduceAverageAndSumWithNoReduceStddevAndVar() {
    // configure rule to reduce AVG and SUM functions
    // other functions like VAR_POP, STDDEV_POP won't be reduced
    final RelOptRule rule = AggregateReduceFunctionsRule.Config.DEFAULT
        .withOperandFor(LogicalAggregate.class)
        .withFunctionsToReduce(EnumSet.of(SqlKind.AVG, SqlKind.SUM))
        .toRule();
    final String sql = "select name, stddev_pop(deptno), avg(deptno),"
        + " var_pop(deptno)\n"
        + "from sales.dept group by name";
    sql(sql).withRule(rule).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5000">[CALCITE-5000]
   * Expand rule of `AGGREGATE_REDUCE_FUNCTIONS`, when arg of agg-call exist in the agg's group</a>.
   */
  @Test void testReduceAggregateFunctionsByGroup() {
    final String sql = "select sal, max(sal) as sal_max, min(sal) as sal_min,\n"
        + "avg(sal) sal_avg, any_value(sal) as sal_val, first_value(sal) as sal_first,\n"
        + "last_value(sal) as sal_last\n"
        + "from emp group by sal, deptno";
    sql(sql).withRule(CoreRules.AGGREGATE_REDUCE_FUNCTIONS, CoreRules.PROJECT_MERGE).check();
  }

  @Test void testReduceAllAggregateFunctions() {
    // configure rule to reduce all used functions
    final RelOptRule rule = AggregateReduceFunctionsRule.Config.DEFAULT
        .withOperandFor(LogicalAggregate.class)
        .withFunctionsToReduce(
            EnumSet.of(SqlKind.AVG, SqlKind.SUM, SqlKind.STDDEV_POP,
                SqlKind.STDDEV_SAMP, SqlKind.VAR_POP, SqlKind.VAR_SAMP))
        .toRule();
    final String sql = "select name, stddev_pop(deptno), avg(deptno),"
        + " stddev_samp(deptno), var_pop(deptno), var_samp(deptno)\n"
        + "from sales.dept group by name";
    sql(sql).withRule(rule).check();
  }

  @Test void testReduceWithNonTypePredicate() {
    // Make sure we can reduce with more specificity than just agg function type.
    final RelOptRule rule = AggregateReduceFunctionsRule.Config.DEFAULT
        .withExtraCondition(call -> call.distinctKeys != null)
        .toRule();
    final String sql = "select avg(sal), avg(sal) within distinct (deptno)\n"
        + "from emp";
    sql(sql).withRule(rule).check();
  }

  /** Test case for
  * <a href="https://issues.apache.org/jira/browse/CALCITE-2803">[CALCITE-2803]
  * Identify expanded IS NOT DISTINCT FROM expression when pushing project past join</a>.
  */
  @Test void testPushProjectWithIsNotDistinctFromPastJoin() {
    final String sql = "select e.sal + b.comm from emp e inner join bonus b\n"
        + "on (e.ename || e.job) IS NOT DISTINCT FROM (b.ename || b.job) and e.deptno = 10";
    sql(sql)
        .withRelBuilderSimplify(false)
        .withRule(CoreRules.PROJECT_JOIN_TRANSPOSE)
        .check();
  }

  @Test void testDynamicStarWithUnion() {
    String sql = "(select n_nationkey from SALES.CUSTOMER)\n"
        + "union all\n"
        + "(select n_name from CUSTOMER_MODIFIABLEVIEW)";
    RuleSet ruleSet =
        RuleSets.ofList(EnumerableRules.ENUMERABLE_PROJECT_RULE,
            EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE,
            EnumerableRules.ENUMERABLE_UNION_RULE);
    sql(sql)
        .withVolcanoPlanner(false, p -> ruleSet.forEach(p::addRule))
        .withDynamicTable()
        .check();
  }

  @Test void testFilterAndProjectWithMultiJoin() {
    final HepProgram preProgram = new HepProgramBuilder()
        .addRuleCollection(Arrays.asList(MyFilterRule.INSTANCE, MyProjectRule.INSTANCE))
        .build();

    final FilterMultiJoinMergeRule filterMultiJoinMergeRule =
        FilterMultiJoinMergeRule.Config.DEFAULT
            .withOperandFor(MyFilter.class, MultiJoin.class)
            .toRule();

    final ProjectMultiJoinMergeRule projectMultiJoinMergeRule =
        ProjectMultiJoinMergeRule.Config.DEFAULT
            .withOperandFor(MyProject.class, MultiJoin.class)
            .toRule();

    HepProgram program = new HepProgramBuilder()
        .addRuleCollection(
            Arrays.asList(
                CoreRules.JOIN_TO_MULTI_JOIN,
                filterMultiJoinMergeRule,
                projectMultiJoinMergeRule))
        .build();

    sql("select * from emp e1 left outer join dept d on e1.deptno = d.deptno where d.deptno > 3")
        .withPre(preProgram).withProgram(program).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3151">[CALCITE-3151]
   * RexCall's Monotonicity is not considered in determining a Calc's
   * collation</a>. */
  @Test void testMonotonicityUDF() {
    final SqlFunction monotonicityFun =
        SqlBasicFunction.create("MONOFUN", ReturnTypes.BIGINT,
            OperandTypes.NILADIC, SqlFunctionCategory.USER_DEFINED_FUNCTION)
            .withDeterministic(false)
            .withMonotonicityInference(call -> SqlMonotonicity.INCREASING);

    // Build a tree equivalent to the SQL
    // SELECT sal, MONOFUN() AS n FROM emp
    final RelBuilder builder =
        RelBuilder.create(RelBuilderTest.config().build());
    final RelNode root =
        builder.scan("EMP")
            .project(builder.field("SAL"),
                builder.alias(builder.call(monotonicityFun), "M"))
            .build();

    HepProgram preProgram = new HepProgramBuilder().build();
    HepPlanner prePlanner = new HepPlanner(preProgram);
    prePlanner.setRoot(root);
    final RelNode relBefore = prePlanner.findBestExp();
    final RelCollation collationBefore =
        relBefore.getTraitSet().getTrait(RelCollationTraitDef.INSTANCE);

    HepProgram hepProgram = new HepProgramBuilder()
        .addRuleInstance(CoreRules.PROJECT_TO_CALC)
        .build();

    HepPlanner hepPlanner = new HepPlanner(hepProgram);
    hepPlanner.setRoot(root);
    final RelNode relAfter = hepPlanner.findBestExp();
    final RelCollation collationAfter =
        relAfter.getTraitSet().getTrait(RelCollationTraitDef.INSTANCE);

    assertThat(collationAfter, is(collationBefore));
  }

  @Test void testPushFilterWithIsNotDistinctFromPastJoin() {
    String sql = "SELECT * FROM "
        + "emp t1 INNER JOIN "
        + "emp t2 "
        + "ON t1.deptno = t2.deptno "
        + "WHERE t1.ename is not distinct from t2.ename";
    sql(sql).withRule(CoreRules.FILTER_INTO_JOIN).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3997">[CALCITE-3997]
   * Logical rules applied on physical operator but failed handle
   * traits</a>. */
  @Test void testMergeJoinCollation() {
    final String sql = "select r.ename, s.sal from\n"
        + "sales.emp r join sales.bonus s\n"
        + "on r.ename=s.ename where r.sal+1=s.sal";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRelTraitDef(RelCollationTraitDef.INSTANCE);
          RelOptUtil.registerDefaultRules(p, false, false);
        })
        .check();
  }

  /**
   * Custom implementation of {@link Filter} for use
   * in test case to verify that {@link FilterMultiJoinMergeRule}
   * can be created with any {@link Filter} and not limited to
   * {@link org.apache.calcite.rel.logical.LogicalFilter}.
   */
  private static class MyFilter extends Filter {
    MyFilter(RelOptCluster cluster, RelTraitSet traitSet,
        RelNode input, RexNode condition) {
      super(cluster, traitSet, input, condition);
    }

    @Override public MyFilter copy(RelTraitSet traitSet, RelNode input,
        RexNode condition) {
      return new MyFilter(getCluster(), traitSet, input, condition);
    }
  }

  /**
   * Rule to transform {@link LogicalFilter} into
   * custom MyFilter.
   */
  public static class MyFilterRule extends RelRule<MyFilterRule.Config> {
    static final MyFilterRule INSTANCE = ImmutableMyFilterRuleConfig.builder()
        .build()
        .withOperandSupplier(b ->
            b.operand(LogicalFilter.class).anyInputs())
        .as(Config.class)
        .toRule();

    protected MyFilterRule(Config config) {
      super(config);
    }

    @Override public void onMatch(RelOptRuleCall call) {
      final LogicalFilter logicalFilter = call.rel(0);
      final RelNode input = logicalFilter.getInput();
      final MyFilter myFilter =
          new MyFilter(input.getCluster(), input.getTraitSet(), input,
              logicalFilter.getCondition());
      call.transformTo(myFilter);
    }

    /** Rule configuration. */
    @Value.Immutable
    @Value.Style(typeImmutable = "ImmutableMyFilterRuleConfig")
    public interface Config extends RelRule.Config {
      @Override default MyFilterRule toRule() {
        return new MyFilterRule(this);
      }
    }
  }

  /**
   * Custom implementation of {@link Project} for use
   * in test case to verify that {@link ProjectMultiJoinMergeRule}
   * can be created with any {@link Project} and not limited to
   * {@link org.apache.calcite.rel.logical.LogicalProject}.
   */
  private static class MyProject extends Project {
    MyProject(
        RelOptCluster cluster,
        RelTraitSet traitSet,
        RelNode input,
        List<? extends RexNode> projects,
        RelDataType rowType) {
      super(cluster, traitSet, ImmutableList.of(), input, projects, rowType, ImmutableSet.of());
    }

    public MyProject copy(RelTraitSet traitSet, RelNode input,
        List<RexNode> projects, RelDataType rowType) {
      return new MyProject(getCluster(), traitSet, input, projects, rowType);
    }
  }

  /**
   * Rule to transform {@link LogicalProject} into custom
   * MyProject.
   */
  public static class MyProjectRule
      extends RelRule<MyProjectRule.Config> {
    static final MyProjectRule INSTANCE = ImmutableMyProjectRuleConfig.builder().build()
        .withOperandSupplier(b -> b.operand(LogicalProject.class).anyInputs())
        .as(Config.class)
        .toRule();

    protected MyProjectRule(Config config) {
      super(config);
    }

    @Override public void onMatch(RelOptRuleCall call) {
      final LogicalProject logicalProject = call.rel(0);
      final RelNode input = logicalProject.getInput();
      final MyProject myProject =
          new MyProject(input.getCluster(), input.getTraitSet(), input,
              logicalProject.getProjects(), logicalProject.getRowType());
      call.transformTo(myProject);
    }

    /** Rule configuration. */
    @Value.Immutable
    @Value.Style(typeImmutable = "ImmutableMyProjectRuleConfig")
    public interface Config extends RelRule.Config {
      @Override default MyProjectRule toRule() {
        return new MyProjectRule(this);
      }
    }
  }

  @Test void testSortJoinCopyInnerJoinOrderBy() {
    final String sql = "select * from sales.emp join sales.dept on\n"
        + "sales.emp.deptno = sales.dept.deptno order by sal";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_COPY)
        .check();
  }

  @Test void testSortJoinCopyInnerJoinOrderByLimit() {
    final String sql = "select * from sales.emp e join (\n"
        + "  select * from sales.dept d) d on e.deptno = d.deptno\n"
        + "order by sal limit 10";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_COPY)
        .check();
  }

  @Test void testSortJoinCopyInnerJoinOrderByTwoFields() {
    final String sql = "select * from sales.emp e join  sales.dept d on\n"
        + " e.deptno = d.deptno order by e.sal,d.name";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withRule(CoreRules.SORT_JOIN_COPY)
        .check();
  }

  @Test void testSortJoinCopySemiJoinOrderBy() {
    final String sql = "select * from sales.dept d where d.deptno in\n"
        + " (select e.deptno from sales.emp e) order by d.deptno";
    sql(sql)
        .withExpand(true)
        .withPreRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .withRule(CoreRules.SORT_JOIN_COPY)
        .check();
  }

  @Test void testSortJoinCopySemiJoinOrderByLimitOffset() {
    final String sql = "select * from sales.dept d where d.deptno in\n"
        + " (select e.deptno from sales.emp e) order by d.deptno limit 10 offset 2";
    // Do not copy the limit and offset
    sql(sql)
        .withExpand(true)
        .withPreRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .withRule(CoreRules.SORT_JOIN_COPY)
        .check();
  }

  @Test void testSortJoinCopySemiJoinOrderByOffset() {
    final String sql = "select * from sales.dept d where d.deptno in"
        + " (select e.deptno from sales.emp e) order by d.deptno offset 2";
    // Do not copy the offset
    sql(sql)
        .withExpand(true)
        .withPreRule(CoreRules.PROJECT_TO_SEMI_JOIN)
        .withRule(CoreRules.SORT_JOIN_COPY)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6824">[CALCITE-6824]
   * Subquery in join conditions rewrite fails if referencing a column
   * from the right-hand side table</a>. */
  @Test void testJoinSubQueryRemoveRuleWithNotIn() {
    final String sql = "SELECT empno FROM emp JOIN dept on "
        + "emp.deptno not in (SELECT deptno FROM dept)";
    sql(sql)
        .withRule(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6824">[CALCITE-6824]
   * Subquery in join conditions rewrite fails if referencing a column
   * from the right-hand side table</a>. */
  @Test void testJoinSubQueryRemoveRuleWithQuantifierSome() {
    final String sql = "SELECT empno FROM emp JOIN dept on "
        + "emp.deptno >= SOME(SELECT deptno FROM dept)";
    sql(sql)
        .withRule(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6824">[CALCITE-6824]
   * Subquery in join conditions rewrite fails if referencing a column
   * from the right-hand side table</a>. */
  @Test void testJoinSubQueryRewriteWithBothSidesColumns() {
    final String sql = "SELECT empno FROM emp JOIN dept on "
        + "emp.deptno + dept.deptno >= SOME(SELECT deptno FROM dept)";
    sql(sql)
        .withRule(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6176">[CALCITE-6176]
   * JOIN_SUB_QUERY_TO_CORRELATE rule incorrectly handles EXISTS in LEFT JOIN ON clause</a>. */
  @Test void testJoinSubQueryRemoveRuleWithNotExists() {
    final String sql = "select *\n"
        + "from (select 1 id) t1\n"
        + "left join (select 2 id) t2\n"
        + "on not exists(select *\n"
        + "               from (select 3 id) p\n"
        + "               where p.id = t2.id)";
    sql(sql)
        .withRule(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6176">[CALCITE-6176]
   * JOIN_SUB_QUERY_TO_CORRELATE rule incorrectly handles EXISTS in LEFT JOIN ON clause</a>. */
  @Test void testJoinSubQueryRemoveRuleWithOrExists() {
    final String sql = "select *\n"
        + "from (select 1 id) t1\n"
        + "left join (select 2 id) t2\n"
        + "on t1.id = t2.id or exists(select *\n"
        + "                           from (select 3 id) p\n"
        + "                           where p.id = t2.id)";
    sql(sql)
        .withRule(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6176">[CALCITE-6176]
   * JOIN_SUB_QUERY_TO_CORRELATE rule incorrectly handles EXISTS in LEFT JOIN ON clause</a>. */
  @Test void testJoinSubQueryRemoveRuleWithOrNotExists() {
    final String sql = "select *\n"
        + "from (select 1 id) t1\n"
        + "left join (select 2 id) t2\n"
        + "on t1.id = t2.id or not exists(select *\n"
        + "                               from (select 3 id) p\n"
        + "                               where p.id = t2.id)";
    sql(sql)
        .withRule(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6176">[CALCITE-6176]
   * JOIN_SUB_QUERY_TO_CORRELATE rule incorrectly handles EXISTS in LEFT JOIN ON clause</a>. */
  @Test void testJoinSubQueryRemoveRuleWithAndNotExists() {
    final String sql = "select *\n"
        + "from (select 1 id union all select 2) t1\n"
        + "left join (select 2 id) t2\n"
        + "on t1.id = t2.id and not exists(select *\n"
        + "                                from (select 3 id) p\n"
        + "                                where p.id = t1.id)";
    sql(sql)
        .withRule(CoreRules.JOIN_SUB_QUERY_TO_CORRELATE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-2295">[CALCITE-2295]
   * Correlated SubQuery with Project will generate error plan</a>. */
  @Test public void testDecorrelationWithProject() throws Exception {
    final String sql = "select sal,\n"
        + "exists (select * from emp_b where emp.deptno = emp_b.deptno)\n"
        + "from sales.emp";
    sql(sql).withSubQueryRules().withLateDecorrelate(true).check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3296">[CALCITE-3296]
   * Decorrelator gives empty result after decorrelating sort rel with
   * null offset and fetch</a>.
   */
  @Test void testDecorrelationWithSort() {
    final String sql = "SELECT e1.empno\n"
        + "FROM emp e1, dept d1 where e1.deptno = d1.deptno\n"
        + "and e1.deptno < 10 and d1.deptno < 15\n"
        + "and e1.sal > (select avg(sal) from emp e2 where e1.empno = e2.empno)\n"
        + "order by e1.empno";

    sql(sql)
        .withRule() // empty program
        .withExpand(true)
        .withDecorrelate(true)
        .checkUnchanged();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3319">[CALCITE-3319]
   * AssertionError for ReduceDecimalsRule</a>. */
  @Test void testReduceDecimal() {
    final String sql = "select ename from emp where sal > cast (100.0 as decimal(4, 1))";
    sql(sql)
        .withRule(CoreRules.FILTER_TO_CALC,
            CoreRules.CALC_REDUCE_DECIMALS)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6870">[CALCITE-6870]
   * The FilterToCalcRule/ProjectToCalcRule should not convert a Filter/Project to Calc
   * when it contains Subquery</a>. */
  @Test void testFilterToCalc() {
    final String sql = "select ename from emp where sal > all (select comm from emp)";
    sql(sql)
        .withRule(CoreRules.FILTER_TO_CALC)
        .checkUnchanged();
  }

  @Test void testProjectToCalc() {
    final String sql = "select  sal > all (select comm from emp) from emp";
    sql(sql)
        .withRule(CoreRules.PROJECT_TO_CALC)
        .checkUnchanged();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6875">[CALCITE-6875]
   * EnumerableFilterRule/EnumerableProjectRule should not convert a Logical Filter/Project
   * to Enumerable Filter/Project when it contains Subquery</a>. */
  @Test void testEnumerableFilterRule() {
    final String sql = "select R_REGIONKEY from SALES.CUSTOMER\n"
        + "where R_REGIONKEY > all (select R_REGIONKEY from SALES.CUSTOMER)";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRelTraitDef(RelDistributionTraitDef.INSTANCE);
          p.addRule(CoreRules.FILTER_SUB_QUERY_TO_CORRELATE);
          p.addRule(EnumerableRules.ENUMERABLE_FILTER_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_JOIN_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_AGGREGATE_RULE);
        })
        .withDynamicTable()
        .check();
  }

  @Test void testEnumerableProjectRule() {
    final String sql = "select R_REGIONKEY > all (select R_REGIONKEY from SALES.CUSTOMER)\n"
        + "from SALES.CUSTOMER";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRelTraitDef(RelDistributionTraitDef.INSTANCE);
          p.addRule(CoreRules.PROJECT_SUB_QUERY_TO_CORRELATE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_JOIN_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_AGGREGATE_RULE);
        })
        .withDynamicTable()
        .check();
  }

  @Test void testEnumerableCalcRule() {
    final String sql = "select FNAME, LNAME\n"
        + "from SALES.CUSTOMER\n"
        + "where CONTACTNO > 10";

    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRelTraitDef(RelDistributionTraitDef.INSTANCE);
          p.addRule(CoreRules.FILTER_TO_CALC);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_CALC_RULE);
        })
        .withDynamicTable()
        .withDecorrelate(true)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-3404">[CALCITE-3404]
   * Treat agg expressions that can ignore distinct constraint as
   * distinct in AggregateExpandDistinctAggregatesRule when all the
   * other agg expressions are distinct and have same arguments</a>. */
  @Test void testMaxReuseDistinctAttrWithMixedOptionality() {
    final String sql = "select sum(distinct deptno), count(distinct deptno), "
        + "max(deptno) from emp";
    sql(sql).withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES).check();
  }

  @Test void testMinReuseDistinctAttrWithMixedOptionality() {
    final String sql = "select sum(distinct deptno), count(distinct deptno), "
        + "min(deptno) from emp";
    sql(sql).withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES).check();
  }

  @Test void testBitAndReuseDistinctAttrWithMixedOptionality() {
    final String sql = "select sum(distinct deptno), count(distinct deptno), "
        + "bit_and(deptno) from emp";
    sql(sql).withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES).check();
  }

  @Test void testBitOrReuseDistinctAttrWithMixedOptionality() {
    final String sql = "select sum(distinct deptno), count(distinct deptno), "
        + "bit_or(deptno) from emp";
    sql(sql).withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES).check();
  }

  @Test void testProjectJoinTransposeItem() {
    ProjectJoinTransposeRule projectJoinTransposeRule =
        CoreRules.PROJECT_JOIN_TRANSPOSE.config
            .withOperandFor(Project.class, Join.class)
            .withPreserveExprCondition(RelOptRulesTest::skipItem)
            .toRule();

    final String sql = "select t1.c_nationkey[0], t2.c_nationkey[0]\n"
        + "from sales.customer as t1\n"
        + "left outer join sales.customer as t2\n"
        + "on t1.c_nationkey[0] = t2.c_nationkey[0]";

    sql(sql)
        .withDynamicTable()
        .withRule(projectJoinTransposeRule)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4317">[CALCITE-4317]
   * RelFieldTrimmer after trimming all the fields in an aggregate
   * should not return a zero field Aggregate</a>. */
  @Test void testProjectJoinTransposeRuleOnAggWithNoFieldsWithTrimmer() {
    fixture()
        .withVolcanoPlanner(false, p -> {
          p.addRelTraitDef(RelDistributionTraitDef.INSTANCE);
          RelOptUtil.registerDefaultRules(p, false, false);
        })
        .withDynamicTable()
        .withTrim(true)
        .relFn(b -> {
          // Build a rel equivalent to sql:
          // SELECT name FROM (SELECT count(*) cnt_star, count(empno) cnt_en FROM sales.emp)
          // cross join sales.dept
          // limit 10

          RelNode left = b.scan("DEPT").build();
          RelNode right = b.scan("EMP")
              .project(b.alias(b.literal(0), "DUMMY"))
              .aggregate(b.groupKey(),
                  b.count(b.field(0)).as("DUMMY_COUNT"))
              .build();

          return b.push(left)
              .push(right)
              .join(JoinRelType.INNER, b.literal(true))
              .project(b.field("DEPTNO"))
              .build();
        })
        .withBefore((f, r) -> {
          final String planBeforeTrimming = NL + RelOptUtil.toString(r);
          f.diffRepos().assertEquals("planBeforeTrimming",
              "${planBeforeTrimming}", planBeforeTrimming);

          RelNode r2 = f.tester.trimRelNode(f.factory, r);
          final String planAfterTrimming = NL + RelOptUtil.toString(r2);
          f.diffRepos().assertEquals("planAfterTrimming",
              "${planAfterTrimming}", planAfterTrimming);
          return r2;
        })
        .withRule(CoreRules.PROJECT_JOIN_TRANSPOSE)
        .checkUnchanged();
  }

  @Test void testSimplifyItemIsNotNull() {
    final String sql = "select *\n"
        + "from sales.customer as t1\n"
        + "where t1.c_nationkey[0] is not null";

    sql(sql)
        .withDynamicTable()
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  @Test void testSimplifyItemIsNull() {
    String sql = "select * from sales.customer as t1 where t1.c_nationkey[0] is null";

    sql(sql)
        .withDynamicTable()
        .withRule(CoreRules.FILTER_REDUCE_EXPRESSIONS)
        .checkUnchanged();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5247">[CALCITE-5247]
   * FilterJoinRule cannot simplify left join to inner join for `WHERE
   * RHS.C1 IS NOT NULL OR RHS.C2 IS NOT NULL`</a>.
   *
   * <p>This tests the case where the condition contains an OR between
   * the IS NOT NULL filters. */
  @Test void testFilterJoinRuleOrIsNotNull() {
    final String sql = "select * from\n"
        + "emp LHS\n"
        + "left join dept RHS on LHS.EMPNO = RHS.DEPTNO\n"
        + "where\n"
        + "RHS.DEPTNO is not null\n"
        + "OR RHS.NAME is not null";

    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-5247">[CALCITE-5247]
   * FilterJoinRule cannot simplify left join to inner join for `WHERE
   * RHS.C1 IS NOT NULL OR RHS.C2 IS NOT NULL`</a>.
   *
   * <p>This tests the case where the condition contains an AND
   * between the IS NOT NULL filters. */
  @Test void testFilterJoinRuleAndIsNotNull() {
    final String sql = "select * from\n"
        + "emp LHS\n"
        + "left join dept RHS on LHS.EMPNO = RHS.DEPTNO\n"
        + "where\n"
        + "RHS.DEPTNO is not null\n"
        + "AND RHS.NAME is not null";

    sql(sql)
        .withRule(CoreRules.FILTER_INTO_JOIN)
        .check();
  }

  @Test void testJoinCommuteRuleWithAlwaysTrueConditionAllowed() {
    checkJoinCommuteRuleWithAlwaysTrueConditionDisallowed(true);
  }

  @Test void testJoinCommuteRuleWithAlwaysTrueConditionDisallowed() {
    checkJoinCommuteRuleWithAlwaysTrueConditionDisallowed(false);
  }

  private void checkJoinCommuteRuleWithAlwaysTrueConditionDisallowed(boolean allowAlwaysTrue) {
    final Function<RelBuilder, RelNode> relFn = b -> b
        .scan("EMP")
        .scan("DEPT")
        .join(JoinRelType.INNER,
            b.literal(true))
        .build();

    JoinCommuteRule.Config ruleConfig = JoinCommuteRule.Config.DEFAULT;
    if (!allowAlwaysTrue) {
      ruleConfig = ruleConfig.withAllowAlwaysTrueCondition(false);
    }

    HepProgram program = new HepProgramBuilder()
        .addMatchLimit(1)
        .addRuleInstance(ruleConfig.toRule())
        .build();
    HepPlanner hepPlanner = new HepPlanner(program);

    if (allowAlwaysTrue) {
      relFn(relFn).withPlanner(hepPlanner).check();
    } else {
      relFn(relFn).withPlanner(hepPlanner).checkUnchanged();
    }
  }

  @Test void testJoinAssociateRuleWithBottomAlwaysTrueConditionAllowed() {
    checkJoinAssociateRuleWithBottomAlwaysTrueCondition(true);
  }

  @Test void testJoinAssociateRuleWithBottomAlwaysTrueConditionDisallowed() {
    checkJoinAssociateRuleWithBottomAlwaysTrueCondition(false);
  }

  private void checkJoinAssociateRuleWithBottomAlwaysTrueCondition(boolean allowAlwaysTrue) {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode bottomLeft = b.scan("EMP").build();
      RelNode bottomRight = b.scan("DEPT").build();
      RelNode top = b.scan("BONUS").build();

      return b.push(bottomLeft)
          .push(bottomRight)
          .join(JoinRelType.INNER,
              b.equals(b.field(2, 0, "DEPTNO"),
                  b.field(2, 1, "DEPTNO")))
          .push(top)
          .join(JoinRelType.INNER,
              b.equals(b.field(2, 0, "JOB"),
                  b.field(2, 1, "JOB")))
          .build();
    };

    JoinAssociateRule.Config ruleConfig = JoinAssociateRule.Config.DEFAULT;
    if (!allowAlwaysTrue) {
      ruleConfig = ruleConfig.withAllowAlwaysTrueCondition(false);
    }

    HepProgram program = new HepProgramBuilder()
        .addMatchLimit(1)
        .addMatchOrder(HepMatchOrder.TOP_DOWN)
        .addRuleInstance(ruleConfig.toRule())
        .build();
    HepPlanner hepPlanner = new HepPlanner(program);

    if (allowAlwaysTrue) {
      relFn(relFn).withPlanner(hepPlanner).check();
    } else {
      relFn(relFn).withPlanner(hepPlanner).checkUnchanged();
    }
  }

  @Test void testJoinAssociateRuleWithTopAlwaysTrueConditionAllowed() {
    checkJoinAssociateRuleWithTopAlwaysTrueCondition(true);
  }

  @Test void testJoinAssociateRuleWithTopAlwaysTrueConditionDisallowed() {
    checkJoinAssociateRuleWithTopAlwaysTrueCondition(false);
  }

  private void checkJoinAssociateRuleWithTopAlwaysTrueCondition(boolean allowAlwaysTrue) {
    final Function<RelBuilder, RelNode> relFn = b -> {

      RelNode bottomLeft = b.scan("EMP").build();
      RelNode bottomRight = b.scan("BONUS").build();
      RelNode top = b.scan("DEPT").build();

      return b.push(bottomLeft)
          .push(bottomRight)
          .join(JoinRelType.INNER,
              b.literal(true))
          .push(top)
          .join(JoinRelType.INNER,
              b.equals(b.field(2, 0, "DEPTNO"),
                  b.field(2, 1, "DEPTNO")))
          .build();
    };

    JoinAssociateRule.Config ruleConfig = JoinAssociateRule.Config.DEFAULT;
    if (!allowAlwaysTrue) {
      ruleConfig = ruleConfig.withAllowAlwaysTrueCondition(false);
    }

    HepProgram program = new HepProgramBuilder()
        .addMatchLimit(1)
        .addMatchOrder(HepMatchOrder.TOP_DOWN)
        .addRuleInstance(ruleConfig.toRule())
        .build();
    HepPlanner hepPlanner = new HepPlanner(program);

    if (allowAlwaysTrue) {
      relFn(relFn).withPlanner(hepPlanner).check();
    } else {
      relFn(relFn).withPlanner(hepPlanner).checkUnchanged();
    }
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4652">[CALCITE-4652]
   * AggregateExpandDistinctAggregatesRule must cast top aggregates to original
   * type</a>.
   *
   * <p>Checks AggregateExpandDistinctAggregatesRule when return type of the SUM
   * aggregate is changed (expanded) by define custom type factory. */
  @Test void testDistinctCountWithExpandSumType() {
    // Define new type system to expand SUM return type.
    RelDataTypeSystemImpl typeSystem = new RelDataTypeSystemImpl() {
      @Override public RelDataType deriveSumType(RelDataTypeFactory typeFactory,
          RelDataType argumentType) {
        switch (argumentType.getSqlTypeName()) {
        case INTEGER:
        case BIGINT:
          return typeFactory.createSqlType(SqlTypeName.DECIMAL);

        default:
          return super.deriveSumType(typeFactory, argumentType);
        }
      }
    };

    // Expected plan:
    // LogicalProject(EXPR$0=[CAST($0):BIGINT NOT NULL], EXPR$1=[$1])
    //   LogicalAggregate(group=[{}], EXPR$0=[$SUM0($1)], EXPR$1=[COUNT($0)])
    //     LogicalAggregate(group=[{0}], EXPR$0=[COUNT()])
    //       LogicalProject(COMM=[$6])
    //         LogicalTableScan(table=[[CATALOG, SALES, EMP]])
    //
    // The top 'LogicalProject' must be added in case SUM type is expanded
    // because type of original expression 'COUNT(DISTINCT comm)' is BIGINT
    // and type of SUM (of BIGINT) is DECIMAL.
    sql("SELECT count(comm), COUNT(DISTINCT comm) FROM emp")
        .withFactory(f -> f.withTypeSystem(ignore -> typeSystem))
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-4818">[CALCITE-4818]
   * AggregateExpandDistinctAggregatesRule must infer correct data type for top
   * aggregate calls</a>.
   *
   * <p>Checks AggregateExpandDistinctAggregatesRule when return type of the SUM
   * aggregate is changed (expanded) by define custom type factory. */
  @Test void testSumAndDistinctSumWithExpandSumType() {
    // Define new type system to expand SUM return type.
    RelDataTypeSystemImpl typeSystem = new RelDataTypeSystemImpl() {
      @Override public RelDataType deriveSumType(RelDataTypeFactory typeFactory,
          RelDataType argumentType) {
        switch (argumentType.getSqlTypeName()) {
        case INTEGER:
          return typeFactory.createSqlType(SqlTypeName.BIGINT);
        case BIGINT:
          return typeFactory.createSqlType(SqlTypeName.DECIMAL);

        default:
          return super.deriveSumType(typeFactory, argumentType);
        }
      }
    };

    // Expected plan:
    // LogicalProject(EXPR$0=[CAST($0):BIGINT], EXPR$1=[$1])
    //  LogicalAggregate(group=[{}], EXPR$0=[SUM($1)], EXPR$1=[SUM($0)]) // RowType[DECIMAL, BIGINT]
    //    LogicalAggregate(group=[{0}], EXPR$0=[SUM($0)])  // RowType[INTEGER, BIGINT]
    //      LogicalProject(COMM=[$6])
    //        LogicalTableScan(table=[[CATALOG, SALES, EMP]])
    //
    // The top 'LogicalProject' must be added in case SUM type is expanded
    // because type of original expression 'COUNT(DISTINCT comm)' is BIGINT
    // and type of SUM (of BIGINT) is DECIMAL.
    sql("SELECT SUM(comm), SUM(DISTINCT comm) FROM emp")
        .withFactory(f -> f.withTypeSystem(ignore -> typeSystem))
        .withRule(CoreRules.AGGREGATE_EXPAND_DISTINCT_AGGREGATES_TO_JOIN)
        .check();
  }

  @Test void testLoptOptimizeJoinRulePrioritizeSelfJoin() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.JOIN_TO_MULTI_JOIN)
        .build();

    sql("select e.empno from emp e"
        + " inner join dept d on d.deptno = e.deptno"
        + " inner join emp e2 on e.empno = e2.empno")
        .withPre(program)
        .withRule(CoreRules.MULTI_JOIN_OPTIMIZE)
        .check();
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6788">[CALCITE-6788]
   * LoptOptimizeJoinRule should be able to delegate costs to the planner</a>. */
  @Test void testLoptOptimizeJoinRuleWithDefaultCost() {
    // Use the default rule
    checkLoptOptimizeJoinRule(CoreRules.MULTI_JOIN_OPTIMIZE);
  }

  /** Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6788">[CALCITE-6788]
   * LoptOptimizeJoinRule should be able to delegate costs to the planner</a>. */
  @Test void testLoptOptimizeJoinRuleWithSpecialCost() {
    // Use an ad-hoc version of the rule that uses planner#getCost instead of mq#getCumulativeCost
    checkLoptOptimizeJoinRule(LoptOptimizeJoinRule.Config.DEFAULT
        .withCostFunction((c, r) -> c.getPlanner().getCost(r, c.getMetadataQuery()))
        .toRule());
  }

  private void checkLoptOptimizeJoinRule(LoptOptimizeJoinRule rule) {
    final HepProgram preProgram = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.JOIN_TO_MULTI_JOIN)
        .build();

    final HepProgram program = HepProgram.builder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(rule)
        .build();

    // Special planner that artificially favors joins on the same table
    final HepPlanner planner = new HepPlanner(program) {
      @Override public RelOptCost getCost(RelNode rel, RelMetadataQuery mq) {
        if (rel instanceof Join
            && rel.getInput(0).stripped() instanceof TableScan
            && rel.getInput(1).stripped() instanceof TableScan) {
          TableScan left = (TableScan) rel.getInput(0).stripped();
          TableScan right = (TableScan) rel.getInput(1).stripped();
          if (left.getTable().equals(right.getTable())) {
            // Tiny cost for self-joins
            return getCostFactory().makeTinyCost();
          }
        }

        // General case: just define a kind of cumulative cost based on the rowCount (to avoid
        // the infinite costs from the Logical operators)
        RelOptCost cost = new RelOptCostImpl(mq.getRowCount(rel));
        for (RelNode input : rel.getInputs()) {
          cost = cost.plus(getCost(input, mq));
        }
        return cost;
      }
    };

    sql("select e1.empno from emp e1"
        + " inner join dept d1 on d1.deptno = e1.deptno"
        + " inner join emp e2 on e1.ename = e2.ename"
        + " inner join dept d2 on d2.deptno = e1.deptno")
        .withPre(preProgram)
        .withPlanner(planner)
        .check();
  }

  /**
   * Test case for
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6874">[CALCITE-6874]
   * FilterCalcMergeRule/ProjectCalcMergeRule should not merge a Filter/Project to Calc
   * when it contains Subquery</a>. */
  @Test void testFilterCalcMergeRule() {
    final String sql = "select deptno from sales.emp where\n"
        + "exists (select deptno from sales.emp where empno < 20)\n";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .addRuleInstance(CoreRules.PROJECT_TO_CALC)
        .build();
    sql(sql)
        .withPre(program)
        .withRule(CoreRules.FILTER_CALC_MERGE)
        .checkUnchanged();
  }

  @Test void testProjectCalcMergeRule() {
    final String sql = "select exists (select deptno from sales.emp)\n"
        + "from (select deptno from sales.emp where empno < 20)\n";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .addRuleInstance(CoreRules.FILTER_TO_CALC)
        .build();
    sql(sql)
        .withPre(program)
        .withRule(CoreRules.PROJECT_CALC_MERGE)
        .checkUnchanged();
  }

  /**
   * Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6850">[CALCITE-6850]
   * ProjectRemoveRule with two Projects
   * does not keep field names from the top one</a>. */
  @Test void testProjectRemoveRule() {
    final Function<RelBuilder, RelNode> relFn = b -> {
      RelNode r = b.scan("DEPT")
          .project(
              b.call(SqlStdOperatorTable.PLUS,
              ImmutableList.of(b.field(0), b.literal(1))))
          .build();
      b.push(r);
      return LogicalProject.create(r, ImmutableList.of(),
          ImmutableList.of(b.field(0)),
          ImmutableList.of("newAlias"), ImmutableSet.of());
    };
    HepProgramBuilder builder = new HepProgramBuilder();
    builder.addRuleInstance(CoreRules.PROJECT_REMOVE);
    HepPlanner planner =
        new HepPlanner(builder.build(), null, true, null, RelOptCostImpl.FACTORY);
    fixture().withRelBuilderConfig(a -> a.withBloat(-1))
        .relFn(relFn).withPlanner(planner).check();
  }

  @Test void testChainJoinDphypJoinReorder() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from "
        + "emp, emp_address, dept, dept_nested "
        + "where emp.deptno + emp_address.empno = dept.deptno + dept_nested.deptno "
        + "and emp.empno = emp_address.empno "
        + "and dept.deptno = dept_nested.deptno")
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testStarJoinDphypJoinReorder() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from "
        + "emp, emp_b, emp_address, dept, dept_nested "
        + "where emp.empno = emp_b.empno "
        + "and emp.empno = emp_address.empno "
        + "and emp.deptno = dept.deptno "
        + "and emp.deptno = dept_nested.deptno "
        + "and emp_b.sal + emp_address.empno = dept.deptno + dept_nested.deptno")
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testCycleJoinDphypJoinReorder() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from "
        + "emp, emp_b, dept, dept_nested "
        + "where emp.empno = emp_b.empno "
        + "and emp_b.deptno = dept.deptno "
        + "and dept.name = dept_nested.name "
        + "and dept_nested.deptno = emp.deptno "
        + "and emp.sal + emp_b.sal = dept.deptno + dept_nested.deptno")
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /**
   * Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6836">[CALCITE-6836]
   * Add Rule to convert INTERSECT to EXISTS</a>. */
  @Test void testIntersectToExistsRuleOneField() {
    String sql = "SELECT a.ename FROM emp AS a\n"
        + "INTERSECT\n"
        + "SELECT b.name FROM dept AS b";
    sql(sql).withRule(CoreRules.INTERSECT_TO_EXISTS)
        .check();
  }

  /**
   * Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6836">[CALCITE-6836]
   * Add Rule to convert INTERSECT to EXISTS</a>. */
  @Test void testIntersectToExistsRulePrimaryKey() {
    String sql = "SELECT a.empno FROM emp AS a\n"
        + "INTERSECT\n"
        + "SELECT b.empno FROM emp AS b";
    sql(sql).withRule(CoreRules.INTERSECT_TO_EXISTS)
        .check();
  }

  /**
   * Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6836">[CALCITE-6836]
   * Add Rule to convert INTERSECT to EXISTS</a>. */
  @Test void testIntersectToExistsRuleMultiFields() {
    String sql = "SELECT a.ename, a.job FROM emp AS a\n"
        + "INTERSECT\n"
        + "SELECT b.ename, b.job FROM emp AS b";
    sql(sql).withRule(CoreRules.INTERSECT_TO_EXISTS)
        .check();
  }

  /**
   * Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6836">[CALCITE-6836]
   * Add Rule to convert INTERSECT to EXISTS</a>. */
  @Test void testIntersectToExistsRuleMultiIntersect() {
    String sql = "SELECT a.ename FROM emp AS a\n"
        + "INTERSECT\n"
        + "SELECT b.name FROM dept AS b\n"
        + "INTERSECT\n"
        + "SELECT c.ename FROM emp AS c";
    sql(sql).withRule(CoreRules.INTERSECT_TO_EXISTS)
        .check();
  }

  /**
   * Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6836">[CALCITE-6836]
   * Add Rule to convert INTERSECT to EXISTS</a>. */
  @Test void testIntersectToExistsRuleWithAll() {
    String sql = "SELECT a.ename FROM emp AS a\n"
        + "INTERSECT ALL\n"
        + "SELECT b.name FROM dept AS b";
    sql(sql).withRule(CoreRules.INTERSECT_TO_EXISTS)
        .checkUnchanged();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6891">[CALCITE-6891]
   * Implement IntersectReorderRule</a>. */
  @Test void testIntersectReorderRule() {
    final String sql = "select deptno from emp\n"
        + "intersect\n"
        + "select deptno from emp where deptno > 5\n";
    sql(sql).withVolcanoPlanner(true, p -> {
      p.addRule(CoreRules.INTERSECT_REORDER);
      p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_INTERSECT_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_FILTER_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
    }).check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6891">[CALCITE-6891]
   * Implement IntersectReorderRule</a>. */
  @Test void testIntersectReorderRuleSameRowCount() {
    final String sql = "select deptno from emp where deptno > 10\n"
        + "intersect\n"
        + "select deptno from emp where deptno > 5\n";
    sql(sql).withVolcanoPlanner(true, p -> {
      p.addRule(CoreRules.INTERSECT_REORDER);
      p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_INTERSECT_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_FILTER_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
    }).check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6891">[CALCITE-6891]
   * Implement IntersectReorderRule</a>. */
  @Test void testIntersectReorderRuleAll() {
    final String sql = "select deptno from emp\n"
        + "intersect all\n"
        + "select deptno from emp where deptno > 5\n";
    sql(sql).withVolcanoPlanner(true, p -> {
      p.addRule(CoreRules.INTERSECT_REORDER);
      p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_INTERSECT_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_FILTER_RULE);
      p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
    }).check();
  }

  @Test void testExpandFilterDisjunctionForTable() {
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.EXPAND_FILTER_DISJUNCTION_GLOBAL)
        .build();

    sql("select e.empno from emp as e, empnullables as en "
        + "where e.empno = en.empno "
        + "and "
        + "( "
        + "  (e.mgr > 100 and en.sal < 200) "
        + "  or "
        + "  e.comm < 50 "
        + ") ")
        .withPre(program)
        .withRule(CoreRules.FILTER_INTO_JOIN)
        .check();
  }

  @Test void testExpandJoinDisjunctionForTable() {
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.EXPAND_JOIN_DISJUNCTION_GLOBAL)
        .build();

    sql("select e.empno from emp as e inner join empnullables as en "
        + "on e.empno = en.empno "
        + "and "
        + "( "
        + "  (e.mgr > 100 and en.sal < 200) "
        + "  or "
        + "  (e.comm < 50 and en.deptno > 10) "
        + ") ")
        .withPre(program)
        .withRule(CoreRules.JOIN_CONDITION_PUSH)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6973">[CALCITE-6973]
   * Add rule for convert Minus to Filter</a>. */
  @Test void testMinusToFilterRuleWithTwoFilters() {
    final String sql = "SELECT mgr, comm FROM emp WHERE mgr = 12\n"
        + "EXCEPT\n"
        + "SELECT mgr, comm FROM emp WHERE comm = 5\n";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.MINUS_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-6973">[CALCITE-6973]
   * Add rule for convert Minus to Filter</a>. */
  @Test void testMinusToFilterRuleWithOneFilter() {
    final String sql = "SELECT mgr, comm FROM emp\n"
        + "EXCEPT\n"
        + "SELECT mgr, comm FROM emp WHERE comm = 5\n";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.MINUS_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testMinusToFilterRuleNWayWithMultiSources() {
    final String sql = "SELECT deptno FROM emp WHERE deptno > 6\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM dept WHERE deptno > 8\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM emp WHERE deptno > 12\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM dept WHERE deptno > 10\n";
    sql(sql)
        .withPreRule(CoreRules.MINUS_MERGE, CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.MINUS_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testMinusToFilterRuleFourWayWithForSources() {
    final String sql = "SELECT deptno FROM emp WHERE deptno = 12\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM dept WHERE deptno = 5\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM empnullables WHERE deptno = 6\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM deptnullables WHERE deptno = 10\n";
    sql(sql)
        .withPreRule(CoreRules.MINUS_MERGE, CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.MINUS_FILTER_TO_FILTER)
        .checkUnchanged();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testMinusToFilterNWayWithSubquery() {
    final String sql = "SELECT deptno FROM emp WHERE deptno > 12\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM dept WHERE deptno = 5\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM emp e1 WHERE EXISTS (\n"
        + "    SELECT 1 FROM emp e2\n"
        + "    WHERE e2.comm = e1.comm)\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM dept WHERE deptno = 10\n"
        + "EXCEPT\n"
        + "SELECT deptno FROM emp WHERE deptno > 20\n";
    sql(sql)
        .withPreRule(CoreRules.MINUS_MERGE, CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.MINUS_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testUnionToFilterRuleWithTwoFilters() {
    final String sql = "SELECT mgr, comm FROM emp WHERE mgr = 12\n"
        + "UNION\n"
        + "SELECT mgr, comm FROM emp WHERE comm = 5\n";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.UNION_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testUnionToFilterRuleWithOneFilter() {
    final String sql = "SELECT mgr, comm FROM emp\n"
        + "UNION\n"
        + "SELECT mgr, comm FROM emp WHERE comm = 5\n";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.UNION_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testUnionToFilterRuleNWayWithMultiSources() {
    final String sql = "SELECT deptno FROM emp WHERE deptno = 12\n"
        + "UNION\n"
        + "SELECT deptno FROM dept WHERE deptno = 5\n"
        + "UNION\n"
        + "SELECT deptno FROM emp WHERE deptno = 6\n"
        + "UNION\n"
        + "SELECT deptno FROM dept WHERE deptno = 10\n";
    sql(sql)
        .withPreRule(CoreRules.UNION_MERGE, CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.UNION_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testIntersectToFilterRuleWithTwoFilters() {
    final String sql = "SELECT mgr, comm FROM emp WHERE mgr = 12\n"
        + "INTERSECT\n"
        + "SELECT mgr, comm FROM emp WHERE comm = 5\n";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.INTERSECT_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testIntersectToFilterRuleWithOneFilter() {
    final String sql = "SELECT mgr, comm FROM emp\n"
        + "INTERSECT\n"
        + "SELECT mgr, comm FROM emp WHERE comm = 5\n";
    sql(sql)
        .withPreRule(CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.INTERSECT_FILTER_TO_FILTER)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7002">[CALCITE-7002]
   * Create an optimization rule to eliminate UNION
   * from the same source with different filters</a>. */
  @Test void testIntersectToFilterRuleNWayWithMultiSources() {
    final String sql = "SELECT deptno FROM emp WHERE deptno < 12\n"
        + "INTERSECT\n"
        + "SELECT deptno FROM dept WHERE deptno > 5\n"
        + "INTERSECT\n"
        + "SELECT deptno FROM emp WHERE deptno > 6\n"
        + "INTERSECT\n"
        + "SELECT deptno FROM dept WHERE deptno < 10\n";
    sql(sql)
        .withPreRule(CoreRules.INTERSECT_MERGE, CoreRules.PROJECT_FILTER_TRANSPOSE)
        .withRule(CoreRules.INTERSECT_FILTER_TO_FILTER)
        .check();
  }

  @Test void testExpandFilterDisjunctionForJoinInput() {
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.EXPAND_FILTER_DISJUNCTION_LOCAL)
        .build();

    sql("select e.empno from emp as e, empnullables as en, emp_b as eb\n"
        + "where e.empno = en.empno and e.empno = eb.empno\n"
        + "and\n"
        + "(\n"
        + "  (e.mgr > 100 and eb.sal < 200)\n"
        + "  or\n"
        + "  (en.comm < 50 and eb.deptno > 10)\n"
        + ")")
        .withPre(program)
        .withRule(CoreRules.JOIN_CONDITION_PUSH, CoreRules.FILTER_INTO_JOIN)
        .check();
  }

  @Test void testExpandJoinDisjunctionForJoinInput() {
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.EXPAND_JOIN_DISJUNCTION_LOCAL)
        .build();

    sql("select e.empno from emp as e inner join empnullables as en\n"
        + " on e.empno = en.empno\n"
        + " inner join emp_b as eb\n"
        + " on e.empno = eb.empno and\n"
        + "(\n"
        + "  (e.mgr > 100 and eb.sal < 200)\n"
        + "  or\n"
        + "  (en.comm < 50 and eb.deptno > 10)\n"
        + ")")
        .withPre(program)
        .withRule(CoreRules.JOIN_CONDITION_PUSH, CoreRules.FILTER_INTO_JOIN)
        .check();
  }

  @Test void testAggregateMinMaxToLimitRule() {
    final String sql = "select min(deptno), max(deptno) from emp";
    sql(sql).withRule(CoreRules.AGGREGATE_MIN_MAX_TO_LIMIT)
        .check();
  }

  @Test void testOuterJoinForDphyp() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from "
        + "emp_address inner join emp on emp_address.empno = emp.empno "
        + "left join dept on emp.deptno = dept.deptno "
        + "inner join dept_nested on dept.deptno = dept_nested.deptno")
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testSemiJoinForDphyp() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.PROJECT_MERGE)
        .addRuleInstance(CoreRules.PROJECT_TO_SEMI_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from emp, emp_address, salgrade "
        + "where emp_address.empno = emp.empno and emp.sal = salgrade.hisal "
        + "and exists(select * from emp_b where emp_address.empno = emp_b.empno)")
        .withExpand(true)
        .withDecorrelate(true)
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testCartesianProductForDphyp() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from emp_address, emp, emp_b, dept "
        + "where emp_address.empno = emp.empno "
        + "and dept.deptno = emp.deptno")
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testComplexPredicateForDphyp() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.FILTER_INTO_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from "
        + "emp, emp_b, dept, dept_nested "
        + "where emp.deptno + emp_b.empno = dept.deptno + dept_nested.deptno "
        + "and (emp.empno = emp_b.empno or emp.ename = emp_b.ename) "
        + "and sqrt(dept.deptno + dept_nested.deptno) > 2")
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testInnerLeftSemiJoinTypeForDphyp() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.PROJECT_MERGE)
        .addRuleInstance(CoreRules.PROJECT_TO_SEMI_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from "
        + "emp_address inner join emp on emp_address.empno = emp.empno "
        + "left join dept on emp.deptno = dept.deptno "
        + "where exists(select * from dept_nested where dept.deptno = dept_nested.deptno)")
        .withExpand(true)
        .withDecorrelate(true)
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testLeftInnerSemiJoinTypeForDphyp() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.PROJECT_MERGE)
        .addRuleInstance(CoreRules.PROJECT_TO_SEMI_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from "
        + "emp_address left join emp on emp_address.empno = emp.empno "
        + "inner join dept on emp.deptno = dept.deptno "
        + "where exists(select * from dept_nested where dept.deptno = dept_nested.deptno)")
        .withExpand(true)
        .withDecorrelate(true)
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  @Test void testFullLeftSemiJoinTypeForDphyp() {
    HepProgram program = new HepProgramBuilder()
        .addMatchOrder(HepMatchOrder.BOTTOM_UP)
        .addRuleInstance(CoreRules.PROJECT_MERGE)
        .addRuleInstance(CoreRules.PROJECT_TO_SEMI_JOIN)
        .addRuleInstance(CoreRules.JOIN_TO_HYPER_GRAPH)
        .build();

    sql("select emp.empno from "
        + "emp_address full join emp on emp_address.empno = emp.empno "
        + "left join dept on emp.deptno = dept.deptno "
        + "where exists(select * from dept_nested where dept.deptno = dept_nested.deptno)")
        .withExpand(true)
        .withDecorrelate(true)
        .withPre(program)
        .withRule(CoreRules.HYPER_GRAPH_OPTIMIZE, CoreRules.PROJECT_REMOVE, CoreRules.PROJECT_MERGE)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7047">[CALCITE-7047]
   * Improve Volcano planner selection of sort conversion rules</a>. */
  @Test void testLimitSortOnlyLimit() {
    String sql = "select * from emp limit 10";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
        })
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7047">[CALCITE-7047]
   * Improve Volcano planner selection of sort conversion rules</a>. */
  @Test void testLimitSortOnlyOffset() {
    String sql = "select * from emp offset 10";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
        })
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7047">[CALCITE-7047]
   * Improve Volcano planner selection of sort conversion rules</a>. */
  @Test void testLimitSortOnlyOrderBy() {
    String sql = "select * from emp order by deptno";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
        })
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7047">[CALCITE-7047]
   * Improve Volcano planner selection of sort conversion rules</a>. */
  @Test void testLimitSortOrderByLimit() {
    String sql = "select * from emp order by deptno limit 10";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
        })
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7047">[CALCITE-7047]
   * Improve Volcano planner selection of sort conversion rules</a>. */
  @Test void testLimitSortOrderByOffsetLimit() {
    String sql = "select * from emp order by deptno limit 10 offset 5";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
        })
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7047">[CALCITE-7047]
   * Improve Volcano planner selection of sort conversion rules</a>. */
  @Test void testLimitSortOrderByOffset() {
    String sql = "select * from emp order by deptno offset 5";
    sql(sql)
        .withVolcanoPlanner(false, p -> {
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
        })
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7077">[CALCITE-7077]
   * Implement a rule to rewrite FULL JOIN as LEFT JOIN and RIGHT JOIN</a>. */
  @Test void testFullJoinToLeftAndRightJoin() {
    final String query = "select * from emp e1\n"
        + "full join emp e2\n"
        + "on e1.sal = e2.sal and e1.mgr is null";
    sql(query)
        .withRule(CoreRules.FULL_TO_LEFT_AND_RIGHT_JOIN)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7077">[CALCITE-7077]
   * Implement a rule to rewrite FULL JOIN as LEFT JOIN and RIGHT JOIN</a>. */
  @Test void testFullJoinToLeftAndRightJoinNonDeterministic() {
    final String query = "select * from emp e1\n"
        + "full join emp e2\n"
        + "on rand() > 0.5";
    sql(query)
        .withRule(CoreRules.FULL_TO_LEFT_AND_RIGHT_JOIN)
        .checkUnchanged();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7086">[CALCITE-7086]
   * Implement a rule that performs the inverse operation of AggregateCaseToFilterRule</a>. */
  @Test void testAggregateFilterToCase() {
    final String sql = "select coalesce(sum_match, 0) as sum0_match, sum_distinct_not_match,\n"
        + " count_distinct_match, count_star_match from (\n"
        + " select\n"
        + " sum(sal) filter(where deptno = 10) as sum_match,\n"
        + " sum(distinct empno) filter(where deptno = 20) as sum_distinct_not_match,\n"
        + " count(distinct deptno) filter(where job = 'CLERK') as count_distinct_match,\n"
        + " count(*) filter(where deptno = 40) as count_star_match\n"
        + " from emp\n"
        + " )";
    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.PROJECT_AGGREGATE_MERGE)
        .build();

    sql(sql)
        .withPre(program)
        .withRule(CoreRules.AGGREGATE_FILTER_TO_CASE)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7104">[CALCITE-7104]
   * Remove duplicate sort keys</a>. */
  @Test void testSortRemoveDuplicateKeys() {
    final String query = "select d1\n"
        + " from (select deptno as d1, deptno as d2 from dept) as tmp\n"
        + " order by d1, d2, d1 desc\n";
    sql(query)
        .withRule(CoreRules.SORT_REMOVE_DUPLICATE_KEYS)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7104">[CALCITE-7104]
   * Remove duplicate sort keys</a>. */
  @Test void testSortRemoveDuplicateKeysJoin() {
    final String query = "select * from (select deptno as d1, deptno as d2 from emp) as t1\n"
        + " join emp t2 on t1.d1 = t2.deptno order by t1.d1, t1.d2, t1.d1 DESC NULLS FIRST";
    sql(query)
        .withRule(CoreRules.SORT_REMOVE_DUPLICATE_KEYS)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7222">[CALCITE-7222]
   * SortRemoveDuplicateKeysRule miss fetch and offset infomation</a>. */
  @Test void testSortRemoveDuplicateKeysWithPK() {
    final String query = "select * from (select empno as a, deptno as b from emp) t"
        + " order by a, b limit 1 offset 2";
    sql(query)
        .withRule(CoreRules.SORT_REMOVE_DUPLICATE_KEYS)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7222">[CALCITE-7222]
   * SortRemoveDuplicateKeysRule miss fetch and offset infomation</a>. */
  @Test void testSortRemoveDuplicateKeysWithLimit() {
    final String query = "select * from (select empno as a, empno as b from emp) t"
        + " order by a, b limit 1 offset 2";
    sql(query)
        .withRule(CoreRules.SORT_REMOVE_DUPLICATE_KEYS)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7116">[CALCITE-7116]
   * Optimize queries with GROUPING SETS by converting them
   * into equivalent UNION ALL of GROUP BY operations</a>. */
  @Test void testAggregateGroupingSetsToUnionRule() {
    final String sql = "SELECT deptno, job, sal, SUM(comm)\n"
        + "FROM emp\n"
        + "GROUP BY GROUPING SETS ((deptno, job), (deptno, sal))";
    sql(sql)
        .withRule(CoreRules.AGGREGATE_GROUPING_SETS_TO_UNION)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7116">[CALCITE-7116]
   * Optimize queries with GROUPING SETS by converting them
   * into equivalent UNION ALL of GROUP BY operations</a>. */
  @Test void testAggregateGroupingSetsToUnionRuleGroupingGrouId() {
    final String sql = "SELECT deptno, job, sal, SUM(comm),\n"
        + " GROUPING(deptno) AS deptno_flag,\n"
        + " GROUPING(job) AS job_flag,\n"
        + " GROUPING(sal) AS sal_flag,\n"
        + " GROUPING(deptno, sal) AS deptno_sal_flag,\n"
        + " GROUP_ID() AS group_id\n"
        + "FROM emp\n"
        + "GROUP BY GROUPING SETS ((deptno, job),(deptno, job, sal),(deptno, sal))";

    HepProgram program = new HepProgramBuilder()
        .addRuleInstance(CoreRules.AGGREGATE_GROUPING_SETS_TO_UNION)
        .build();

    sql(sql)
        .withProgram(program)
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7178">[CALCITE-7178]
   * FETCH and OFFSET in EnumerableMergeUnionRule do not support BIGINT</a>. */
  @Test void testEnumerableMergeUnionRule() {
    final String sql = "SELECT deptno, job, sal\n"
        + "FROM emp\n"
        + "UNION ALL\n"
        + "SELECT deptno, job, sal\n"
        + "FROM emp order by deptno limit 3000000000 offset 2500000000";
    sql(sql)
        .withPreRule(CoreRules.SORT_PROJECT_TRANSPOSE)
        .withVolcanoPlanner(false, p -> {
          p.addRelTraitDef(RelCollationTraitDef.INSTANCE);
          p.addRule(EnumerableRules.ENUMERABLE_MERGE_UNION_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_SORT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_LIMIT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_PROJECT_RULE);
          p.addRule(EnumerableRules.ENUMERABLE_TABLE_SCAN_RULE);
        })
        .check();
  }

  /** Test case of
   * <a href="https://issues.apache.org/jira/browse/CALCITE-7265">[CALCITE-7265]
   * Verify that relFn works with VolcanoPlanner</a>. */
  @Test void testRelFnWithVolcanoPlanner() {
    final Function<RelBuilder, RelNode> relFn = b ->
        b.scan("EMP")
            .filter(
                b.call(SqlStdOperatorTable.GREATER_THAN,
                    b.field("SAL"),
                    b.literal(2000)))
            .build();

    relFn(relFn)
        .withVolcanoPlanner(false, p -> {
          RelOptUtil.registerDefaultRules(p, false, false);
        })
        .check();
  }
}
